Compare commits

...

46 Commits

Author SHA1 Message Date
The Magician c2ca6d284d Update storage schema, add basic locate feature 2024-09-05 10:53:14 +01:00
The Magician fec8e6cf2d Add connect target to Makefile 2024-09-05 10:52:24 +01:00
The Magician 77a515cba4 Update scantap 2024-09-05 10:52:00 +01:00
The Magician 43ffa2803f Rename struct field to match database column 2024-08-20 18:50:38 +01:00
The Magician eb5ecd5fb4 Add the ability to select storage area 2024-08-20 18:00:23 +01:00
The Magician de116aee20 Update storage UI 2024-08-20 15:28:49 +01:00
The Magician 8fe02f46d6 Add storage areas to database 2024-08-20 12:57:36 +01:00
The Magician e5f5fbaee2 Change storage area types 2024-08-19 16:23:11 +01:00
The Magician b61929a178 Add Scryfall image URL field to database 2024-08-19 16:20:33 +01:00
The Magician 793fb28d38 Remove unused code 2024-08-17 20:19:21 +01:00
The Magician 5c508f035c Add StorageArea table 2024-08-17 20:18:23 +01:00
The Magician 1330c8c507 Add scantap 2024-08-17 20:13:51 +01:00
The Magician 7ffa1b0b24 Add a_scanner_dorkly 2024-08-17 20:13:04 +01:00
The Magician 28014aade7 Update AScannerDarkly 2024-08-17 20:12:06 +01:00
The Magician 8e0d3cc03f Add individual commands for storage settings 2024-06-14 10:50:46 +01:00
The Magician 845204320b Show the number of times the selected card was inserted 2024-06-12 23:37:13 +01:00
The Magician 7031bc7b07 Unify into single REPL 2024-06-12 21:33:50 +01:00
The Magician c45347ec25 Prevent multiple search refreshes when unnecessary 2024-06-12 21:28:15 +01:00
The Magician 828341ce80 Don't crash when exiting fzf without selecting an option 2024-06-12 21:17:35 +01:00
The Magician d835ff2fcd Calculate terminal width when drawing figlets 2024-06-12 18:57:12 +01:00
The Magician 779adfd274 Refresh search options after changing search criteria 2024-06-12 18:53:32 +01:00
The Magician 80eb8696ca Only show update finished message if update run 2024-06-11 17:03:42 +01:00
The Magician ed4c01ce49 Add search filtering UI 2024-06-11 17:02:02 +01:00
The Magician cdf0a562fc Add search filtering to stash UI 2024-06-11 16:28:17 +01:00
The Magician b7263a4bd6 Add CardCondition to database code 2024-06-11 16:28:02 +01:00
The Magician a6f1587612 Implement search criteria filtering 2024-06-11 13:04:28 +01:00
The Magician 2968462011 Fix card condition not being inserted 2024-06-11 10:57:11 +01:00
The Magician 74219fa7aa Only get options when searching 2024-06-11 10:57:02 +01:00
The Magician a6c2f73637 Flesh out REPL UI 2024-06-11 10:43:30 +01:00
The Magician c474d3f8bf Read whole line instead of first space-delimited field 2024-06-11 10:08:07 +01:00
The Magician aad3ba9a57 Implement cli interface 2024-06-10 16:34:16 +01:00
The Magician 2885ec32bc Start search UI 2024-06-10 14:15:10 +01:00
The Magician ecd3fe79d7 Allow quit from anywhere 2024-06-10 13:35:58 +01:00
The Magician d2fcce2d94 Add multi-view UI and update screen 2024-06-10 13:32:45 +01:00
The Magician 4597a17299 Add SessionState 2024-06-08 16:47:06 +01:00
The Magician 017cad1f06 Move keymap to constants file 2024-06-08 16:41:09 +01:00
The Magician 7ead78327c Move main TUI model to home package 2024-06-08 16:32:19 +01:00
The Magician 31d1664619 Add keybindings and help menu 2024-06-05 14:13:29 +01:00
The Magician 4126b3d963 Add splash screen on startup 2024-06-05 13:06:24 +01:00
The Magician 7863b60bed Add CardCondition field to CardStorageLocation 2024-06-03 18:18:35 +01:00
The Magician ef6aa4fde5 Fix insert bug 2024-06-03 18:18:23 +01:00
The Magician 14e5a81ca6 Update primary key type in CardPrinting to fix duplicate update bug 2024-06-03 17:08:15 +01:00
The Magician 343421362c Ignore testing database backups 2024-06-03 16:05:43 +01:00
The Magician 1ab69e8980 Improve UI 2024-06-01 22:54:15 +01:00
The Magician a5934a1d9f Fix position increment bug 2024-06-01 22:18:33 +01:00
The Magician 7dba7e4649 Allow the user to quit properly 2024-05-31 22:44:52 +01:00
65 changed files with 29181 additions and 191 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
penny_dreadful_downloader/legality_data/ penny_dreadful_downloader/legality_data/
a_scanner_dorkly/.venv/
scantap/images/
sevenkeys/sevenkeys
MTG-Card-Identifier_v5.pdf MTG-Card-Identifier_v5.pdf
info.txt info.txt

View File

@ -4,3 +4,4 @@ find_package( OpenCV REQUIRED )
include_directories( ${OpenCV_INCLUDE_DIRS} ) include_directories( ${OpenCV_INCLUDE_DIRS} )
add_executable( AScannerDarkly main.cpp ) add_executable( AScannerDarkly main.cpp )
target_link_libraries( AScannerDarkly ${OpenCV_LIBS} ) target_link_libraries( AScannerDarkly ${OpenCV_LIBS} )
target_link_libraries( AScannerDarkly -llept -ltesseract )

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,7 +1,10 @@
#include <opencv2/opencv.hpp> #include <opencv2/opencv.hpp>
#include <tesseract/baseapi.h>
#include <leptonica/allheaders.h>
#include <string> #include <string>
const std::string WINDOW_NAME = "A Scanner Darkly"; const std::string SCANNER_WINDOW_NAME = "A Scanner Darkly";
const std::string CARD_WINDOW_NAME = "Detected Card";
const std::string CANNY_LOWER_THRESHOLD_TRACKBAR_NAME = "Canny: Lower Threshold"; const std::string CANNY_LOWER_THRESHOLD_TRACKBAR_NAME = "Canny: Lower Threshold";
@ -10,9 +13,112 @@ const std::string ASPECT_RATIO_TOLERANCE_TRACKBAR_NAME = "Aspect Ratio Tolerance
int g_aspect_ratio_tolerance = 1; int g_aspect_ratio_tolerance = 1;
const int MAX_ASPECT_RATIO_TOLERANCE = 100; const int MAX_ASPECT_RATIO_TOLERANCE = 100;
int g_Canny_lower_threshold = 110; int g_Canny_lower_threshold = 195;
const int CANNY_UPPER_THRESHOLD = 255; const int CANNY_UPPER_THRESHOLD = 255;
cv::Mat cropImageToRoi(cv::Mat img, cv::Rect roi) {
return img(roi);
}
cv::Mat detectCardInFrame(cv::Mat frame) {
cv::Mat grayscaleFrame, blurFrame, cannyFrame, cardFrame;
// Perform edge detection
cv::cvtColor(frame, grayscaleFrame, cv::COLOR_BGR2GRAY);
cv::GaussianBlur(grayscaleFrame, blurFrame, cv::Size(5, 5), 2, 2);
cv::Canny(blurFrame, cannyFrame, g_Canny_lower_threshold, CANNY_UPPER_THRESHOLD);
// Perform contour detection
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(cannyFrame, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
// Detect convex hulls
std::vector<std::vector<cv::Point>> hull(contours.size());
for(size_t i = 0; i < contours.size(); i++) {
cv::convexHull(contours[i], hull[i]);
}
// Detect RotatedRects
std::vector<cv::RotatedRect> minRect(contours.size());
for(size_t i = 0; i < contours.size(); i++) {
minRect[i] = cv::minAreaRect(contours[i]);
}
// Draw lines around RotatedRects
/*
cv::Scalar rectangleColor(255, 0, 0, 0);
cv::Point2f rect_points[4];
minRect[i].points(rect_points);
for (int j = 0; j < 4; j++) {
cv::line(frame, rect_points[j], rect_points[(j + 1) % 4], rectangleColor);
}
*/
// Find possible cards
std::vector<cv::RotatedRect> possibleCards(contours.size());
int possibleCount = 0;
for (size_t i = 0; i < minRect.size(); i++) {
float aspectRatio = minRect[i].size.height / minRect[i].size.width;
float aspectRatioTolerance = g_aspect_ratio_tolerance / 100.0f;
if (aspectRatio < (CARD_ASPECT_RATIO - aspectRatioTolerance) || aspectRatio > (CARD_ASPECT_RATIO + aspectRatioTolerance)) {
continue;
}
cv::putText(frame, std::to_string(minRect[i].angle), minRect[i].center, cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 0, 0, 0));
possibleCards[possibleCount] = minRect[i];
possibleCount++;
}
if (possibleCount == 1) {
return cropImageToRoi(frame, possibleCards[0].boundingRect());
} else if (possibleCount > 1) {
// If we have more than one possible match, take the largest one
int largestCardIndex = 0;
int largestCardSize = 0;
for (size_t i = 0; i < possibleCards.size(); i++) {
float size = possibleCards[i].size.width * possibleCards[i].size.height;
if (size >= largestCardSize) {
largestCardIndex = i;
largestCardSize = size;
}
}
// Some basic error checking to ensure the RotatedRect is roughly card-sized
if (largestCardSize >= 14400 && largestCardSize <= 200000) {
return cropImageToRoi(frame, possibleCards[largestCardIndex].boundingRect());
}
}
return cv::Mat();
}
const char* readTextFromImage(cv::InputArray img) {
const char* tempFilename = "temp.bmp";
// Initialize Tesseract api
tesseract::TessBaseAPI* tess = new tesseract::TessBaseAPI();
tess->Init(NULL, "eng");
tess->SetPageSegMode(tesseract::PSM_SPARSE_TEXT);
// Load image into Tesseract
cv::imwrite(tempFilename, img);
Pix* pixd = pixRead(tempFilename);
tess->SetImage(pixd);
tess->Recognize(0);
// Perform OCR
const char* out = tess->GetUTF8Text();
// Cleanup
pixDestroy(&pixd);
std::remove(tempFilename);
return out;
}
int main(int argc, char** argv ) { int main(int argc, char** argv ) {
cv::VideoCapture cap; cv::VideoCapture cap;
cap.open(0); cap.open(0);
@ -22,71 +128,34 @@ int main(int argc, char** argv ) {
return -1; return -1;
} }
std::cout << "Card aspect ratio: " << CARD_ASPECT_RATIO << std::endl; cv::namedWindow(SCANNER_WINDOW_NAME);
cv::namedWindow(WINDOW_NAME); cv::createTrackbar(CANNY_LOWER_THRESHOLD_TRACKBAR_NAME, SCANNER_WINDOW_NAME, &g_Canny_lower_threshold, CANNY_UPPER_THRESHOLD, NULL);
cv::createTrackbar(ASPECT_RATIO_TOLERANCE_TRACKBAR_NAME, SCANNER_WINDOW_NAME, &g_aspect_ratio_tolerance, MAX_ASPECT_RATIO_TOLERANCE, NULL);
cv::createTrackbar(CANNY_LOWER_THRESHOLD_TRACKBAR_NAME, WINDOW_NAME, &g_Canny_lower_threshold, CANNY_UPPER_THRESHOLD, NULL); cv::Mat frame, cardFrame;
cv::createTrackbar(ASPECT_RATIO_TOLERANCE_TRACKBAR_NAME, WINDOW_NAME, &g_aspect_ratio_tolerance, MAX_ASPECT_RATIO_TOLERANCE, NULL);
cv::Mat frame, grayscaleFrame, blurFrame, cannyFrame;
while (true) { while (true) {
cap >> frame; cap >> frame;
//cardFrame = detectCardInFrame(frame);
cv::imshow(SCANNER_WINDOW_NAME, frame);
cv::cvtColor(frame, grayscaleFrame, cv::COLOR_BGR2GRAY); /*
cv::GaussianBlur(grayscaleFrame, blurFrame, cv::Size(5, 5), 2, 2); if (cardFrame.size().width > 0) {
cv::Canny(blurFrame, cannyFrame, g_Canny_lower_threshold, CANNY_UPPER_THRESHOLD); printf("Card detected\n");
while (true) {
std::vector<std::vector<cv::Point> > contours; cv::imshow(CARD_WINDOW_NAME, cardFrame);
std::vector<cv::Vec4i> hierarchy; char c = (char)cv::waitKey(33);
cv::findContours(cannyFrame, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE); if (c == 27) {
cv::destroyWindow(CARD_WINDOW_NAME);
std::vector<std::vector<cv::Point>> hull(contours.size()); break;
for(size_t i = 0; i < contours.size(); i++) {
cv::convexHull(contours[i], hull[i]);
} }
std::vector<cv::RotatedRect> minRect(contours.size());
for(size_t i = 0; i < contours.size(); i++) {
minRect[i] = cv::minAreaRect(contours[i]);
} }
//cv::Scalar contourColor = cv::Scalar(255, 0, 0);
cv::Scalar hullColor = cv::Scalar(0, 255, 0);
cv::Scalar rectangleColor = cv::Scalar(0, 0, 255);
for (size_t i = 0; i< contours.size(); i++) {
//cv::drawContours(frame, contours, (int)i, contourColor, 2, cv::LINE_8, hierarchy, 0);
//cv::drawContours(frame, hull, (int)i, hullColor, 2, cv::LINE_8);
// TODO: A purely aspect-ratio-based detection method returns too many false positives
// inside of a card, even after screwing around with various algorithm parameters.
// We have two potential options to fix this:
// - Only take the largest detected RotatedRect with the correct aspect ratio
// - Perform the camera calibration necessary to take accurate real-world measurements
float aspectRatio = minRect[i].size.height / minRect[i].size.width;
float aspectRatioTolerance = g_aspect_ratio_tolerance / 100.0f;
if (aspectRatio < (CARD_ASPECT_RATIO - aspectRatioTolerance) || aspectRatio > (CARD_ASPECT_RATIO + aspectRatioTolerance)) {
continue;
} }
*/
cv::Point2f rect_points[4];
minRect[i].points(rect_points);
for (int j = 0; j < 4; j++) {
cv::line(frame, rect_points[j], rect_points[(j + 1) % 4], rectangleColor);
}
cv::putText(frame, std::to_string(aspectRatio), minRect[i].center, cv::FONT_HERSHEY_COMPLEX, 1, hullColor);
}
cv::imshow(WINDOW_NAME, frame);
char c = (char)cv::waitKey(33); char c = (char)cv::waitKey(33);
if (c == 27) { if (c == 27) {
break; break;
} else if (c == 's') {
cv::imwrite("output.png", frame);
} }
} }
cv::destroyWindow(WINDOW_NAME);
} }

BIN
AScannerDarkly/temp.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

24
a_scanner_dorkly/main.py Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/python3
import cv2
import numpy as np
mouse_raw = (0, 0)
def clickEvent(event, x, y, flags, params):
global mouse_raw
if event == cv2.EVENT_LBUTTONDOWN:
mouse_raw = (x, y)
print(mouse_raw)
cap = cv2.VideoCapture(0)
cv2.namedWindow("image")
cv2.setMouseCallback("image", clickEvent)
while True:
success, img = cap.read()
cv2.imshow("image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

8
scantap/createtable.sql Normal file
View File

@ -0,0 +1,8 @@
USE sevenkeys;
CREATE TABLE IF NOT EXISTS CardScan (
Id INT PRIMARY KEY AUTO_INCREMENT,
StorageAreaId INT NOT NULL,
FOREIGN KEY (StorageAreaId) REFERENCES StorageArea(Id),
Position INT NOT NULL
);

59
scantap/scantap.sh Executable file
View File

@ -0,0 +1,59 @@
#!/bin/bash
mysql --user=root --password="$(pass show sevenkeys/mysql)" <createtable.sql
printf "scantap: Enter name of storage box: "
read storageAreaName
storageAreaId="$(mysql --silent --silent --user=root --password="$(pass show sevenkeys/mysql)" -e "USE sevenkeys; SELECT Id FROM StorageArea WHERE Name = '$storageAreaName';")"
if test -z "$storageAreaId"; then
echo "scantap: No storage area named \"$storageAreaName\"" >&2
exit 1
fi
startPosition="$(mysql --silent --silent --user=root --password="$(pass show sevenkeys/mysql)" -e "USE sevenkeys; SELECT Position FROM CardScan WHERE StorageAreaId = '$storageAreaId' ORDER BY Position DESC LIMIT 1;")"
if test "$?" -ne "0"; then
echo "scantap: Failed to establish starting position" >&2
exit 1
fi
position=$((startPosition + 1))
printf "scantap: Enter mode (foil/nonfoil): "
read foilMode
outputDir="images/$foilMode"
mkdir -p "$outputDir"
ADD_CARDS=0
echo "scantap: Beginning scan loop"
while true; do
scanimage --format=png --batch=tmp%d.png --batch-count=2 --source "ADF Duplex" --mode Color --page-width 63mm --page-height 88mm 2>/dev/null
# scanimage exits with code 7 if no documents are available in scanner
if test $? -eq 7; then
if test $ADD_CARDS -eq 0; then
ADD_CARDS=1
echo "scantap: No more cards in feeder" >&2
fi
continue
fi
ADD_CARDS=0
if ! test -e tmp1.png || ! test -e tmp2.png; then
echo "scantap: Failed to create temporary image files" >&2
exit 1
fi
key="$(mysql --silent --silent --user=root --password="$(pass show sevenkeys/mysql)" -e "USE sevenkeys; INSERT INTO CardScan (StorageAreaId, Position) VALUES ('$storageAreaId', '$position'); SELECT Id FROM CardScan ORDER BY Id DESC LIMIT 1;")"
if test "$?" -ne "0"; then
echo "scantap: Failed to get key from database" >&2
exit 1
fi
position=$((position + 1))
convert -rotate 180 tmp1.png tmp1.png
convert -rotate 180 tmp2.png tmp2.png
mv tmp1.png "$outputDir/${key}_back.png"
mv tmp2.png "$outputDir/${key}_front.png"
done

View File

@ -1,2 +1,3 @@
cache/ cache/
cmd/database/config.json cmd/database/config.json
sevenkeys.sql

View File

@ -3,3 +3,5 @@ createdb:
removedb: removedb:
rm -rf cache/ rm -rf cache/
mysql --user=root --password=$(shell pass show sevenkeys/mysql) <database/sql/removedb.sql mysql --user=root --password=$(shell pass show sevenkeys/mysql) <database/sql/removedb.sql
connect:
mysql --user=root --password=$(shell pass show sevenkeys/mysql)

102
sevenkeys/cli/mainui.go Normal file
View File

@ -0,0 +1,102 @@
package cli
import (
"database/sql"
"errors"
"fmt"
"os"
"os/exec"
"sevenkeys/database"
"sevenkeys/logic"
)
var output string
func showOutput() {
if output != "" {
fmt.Println(output)
}
}
func MainCliLoop(db *sql.DB) {
var command string
var selectedStorageArea database.StorageArea
var cardLocation database.CardLocation
for {
ShowSplashScreen()
showStorageInfo(os.Stdout, selectedStorageArea)
showSearchCriteria()
showSelectedCard()
showCopiesInserted()
command = GetStringResponse("SEVENKEYS $")
var err error
switch command {
case "q", "quit":
return
case "n", "newstorage":
var storageArea database.StorageArea
storageArea.Name = GetStringResponse("Storage area name:")
storageArea.StorageType = GetStringResponse("Storage area type (Binder/Box):")
err = logic.CreateStorageArea(db, storageArea)
logic.Check(err)
break
case "a", "area":
area, err := logic.SelectStorageArea(db)
logic.Check(err)
selectedStorageArea = area
cardLocation.StorageAreaId = area.Id
break
case "c", "criteria":
getSearchCriteria()
break
case "s", "search":
getSearchOptions(db)
var previousCardPrintingId = cardLocation.CardPrintingId
cardLocation.CardPrintingId, selectedCardPrintingSearchLine, err = logic.Search(searchOptions)
var exitError *exec.ExitError
if errors.As(err, &exitError) {
break
}
logic.Check(err)
output = ""
if cardLocation.CardPrintingId != previousCardPrintingId {
copiesInserted = 0
}
break
case "i", "insert":
insertSelectedCard(db, selectedStorageArea, cardLocation)
break
case "l", "locate":
cardName := GetStringResponse("Card name:")
locations, err := logic.LocateCards(db, cardName)
logic.Check(err)
for _, location := range locations {
fmt.Printf("%s (%s %s) [%s]", location.CardName, location.SetCode, location.CollectorNumber, location.Language)
if location.IsFoil {
fmt.Printf(" FOIL")
}
if location.IsPromo {
fmt.Printf(" PROMO")
}
fmt.Printf(" in %s \"%s\" at position %d\n", location.StorageAreaType, location.StorageAreaName, location.Position)
}
fmt.Scanln()
break
default:
fmt.Println("Unrecognized command:", command)
break
}
}
}

38
sevenkeys/cli/response.go Normal file
View File

@ -0,0 +1,38 @@
package cli
import (
"bufio"
"fmt"
"os"
"sevenkeys/logic"
"strings"
)
func GetStringResponse(prompt string) string {
fmt.Print(prompt + " ")
var response string
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
response = scanner.Text()
return response
}
func GetYesNoResponse(prompt string) bool {
response := GetStringResponse(prompt)
return strings.ToUpper(response) == "Y"
}
func GetTriadicResponse(prompt string) logic.Triadic {
response := GetStringResponse(prompt)
switch strings.ToUpper(response) {
case "Y":
return logic.True
case "N":
return logic.False
default:
return logic.Either
}
}

71
sevenkeys/cli/search.go Normal file
View File

@ -0,0 +1,71 @@
package cli
import (
"database/sql"
"fmt"
"sevenkeys/logic"
)
var (
searchCriteria logic.SearchCriteria = logic.SearchCriteria{
SetCode: "",
Foil: logic.Either,
Promo: logic.Either,
Language: "",
}
shouldRefreshSearch bool = true
searchOptions logic.SearchOptions
)
func getSearchCriteria() {
searchCriteria.SetCode = GetStringResponse("Set code:")
searchCriteria.Foil = GetTriadicResponse("Foil (y/n/E):")
searchCriteria.Promo = GetTriadicResponse("Promo (y/n/E):")
searchCriteria.Language = GetStringResponse("Language:")
shouldRefreshSearch = true
}
func getTriadicDisplay(triadic logic.Triadic) string {
if triadic == logic.True {
return "True"
}
if triadic == logic.False {
return "False"
}
return "Either"
}
func showSearchCriteria() {
fmt.Println("SEARCH CRITERIA")
setCodeDisplay := getInfoDisplay(searchCriteria.SetCode)
foilDisplay := getTriadicDisplay(searchCriteria.Foil)
promoDisplay := getTriadicDisplay(searchCriteria.Promo)
languageDisplay := getInfoDisplay(searchCriteria.Language)
fmt.Println("Set code:", setCodeDisplay)
fmt.Println("Foil:", foilDisplay)
fmt.Println("Promo:", promoDisplay)
fmt.Println("Language:", languageDisplay)
fmt.Print("\n")
}
func showSelectedCard() {
selectedCardDisplay := getInfoDisplay(selectedCardPrintingSearchLine)
fmt.Println("Selected card:", selectedCardDisplay)
}
func getSearchOptions(db *sql.DB) {
if shouldRefreshSearch {
fmt.Println("LOADING")
options, err := logic.GetAllSearchOptions(db, searchCriteria)
logic.Check(err)
searchOptions = options
shouldRefreshSearch = false
fmt.Println("READY")
fmt.Println("RUN")
}
}

View File

@ -0,0 +1,19 @@
package cli
import (
"sevenkeys/figlet"
"github.com/inancgumus/screen"
)
func ClearScreen() {
screen.Clear()
screen.MoveTopLeft()
}
func ShowSplashScreen() {
ClearScreen()
figlet.PrintMsgSlant("SEVENKEYS", "center")
figlet.PrintMsgTerm("the ultimate Magic: the Gathering trading card storage system", "center")
}

55
sevenkeys/cli/storage.go Normal file
View File

@ -0,0 +1,55 @@
package cli
import (
"database/sql"
"fmt"
"io"
"sevenkeys/database"
"sevenkeys/logic"
)
var (
selectedCardPrintingSearchLine string
copiesInserted int
)
func getInfoDisplay(info string) string {
if info == "" {
return "[EMPTY]"
}
return info
}
func showStorageInfo(w io.Writer, area database.StorageArea) {
fmt.Fprint(w, "Selected Storage Area: ")
if area.Name == "" { // TODO: Add a struct method to determine whether it is unset?
fmt.Fprint(w, "[None]\n")
return
}
fmt.Fprintf(w, "%s (%s)\n", area.Name, area.StorageType)
return
}
func showCopiesInserted() {
fmt.Println("Copies inserted:", copiesInserted)
}
func insertSelectedCard(db *sql.DB, area database.StorageArea, location database.CardLocation) {
if area.Name == "" {
output = "No storage area selected."
return
}
if location.CardPrintingId == "" {
output = "No card selected, please [search] for a card printing."
return
}
err := logic.StoreCard(db, location)
logic.Check(err)
copiesInserted++
}

View File

@ -0,0 +1,39 @@
package cli
import (
"bytes"
"sevenkeys/database"
"testing"
)
func Test_showStorageInfo_DisplaysNone_IfSelectedStorageAreaIsUnset(t *testing.T) {
expected := "Selected Storage Area: [None]\n"
var output bytes.Buffer
var area database.StorageArea
showStorageInfo(&output, area)
result := output.String()
if result != expected {
t.Errorf("expected %s, got %s", expected, result)
}
}
func Test_showStorageInfo_DisplaysStorageAreaNameAndType_IfSelectedStorageAreaIsSet(t *testing.T) {
expected := "Selected Storage Area: Test A (Box)\n"
var output bytes.Buffer
area := database.StorageArea{
Id: 1,
Name: "Test A",
Type: "Box",
}
showStorageInfo(&output, area)
result := output.String()
if result != expected {
t.Errorf("expected %s, got %s", expected, result)
}
}

37
sevenkeys/cli/updateui.go Normal file
View File

@ -0,0 +1,37 @@
package cli
import (
"database/sql"
"fmt"
"sevenkeys/logic"
"sevenkeys/logic/scryfall"
)
func RunUpdateCheck(db *sql.DB) {
fmt.Println("Checking for updates...")
bulkData, err := scryfall.GetBulkDataByType(scryfall.BulkDataTypeAllCards)
logic.Check(err)
needsUpdate, err := logic.CheckForUpdates(db, bulkData)
logic.Check(err)
if !needsUpdate {
fmt.Println("No update required.")
return
}
fmt.Println("Update required.")
if GetYesNoResponse("Run update? (y/N)") {
fmt.Println("Running update...")
logic.CreateCacheDirectories()
err = logic.UpdateSets(db)
logic.Check(err)
err = logic.UpdateCards(db, bulkData)
logic.Check(err)
fmt.Println("Update finished.")
}
}

View File

@ -0,0 +1,12 @@
package constants
import (
"database/sql"
tea "github.com/charmbracelet/bubbletea"
)
var (
Program *tea.Program
Database *sql.DB
)

View File

@ -5,24 +5,27 @@ import (
) )
type CardPrinting struct { type CardPrinting struct {
Id int Id string
Name string Name string
SetCode string SetCode string
IsFoil bool IsFoil bool
IsPromo bool IsPromo bool
CollectorNumber string CollectorNumber string
ImageUrl string
Language string Language string
} }
func InsertCardPrinting(db *sql.DB, cardPrinting CardPrinting) error { func InsertCardPrinting(db *sql.DB, cardPrinting CardPrinting) error {
query := `INSERT INTO CardPrinting ( query := `INSERT INTO CardPrinting (
Id,
Name, Name,
SetCode, SetCode,
IsFoil, IsFoil,
IsPromo, IsPromo,
CollectorNumber, CollectorNumber,
ImageUrl,
Language) Language)
VALUES (?, ?, ?, ?, ?, ?);` VALUES (?, ?, ?, ?, ?, ?, ?, ?);`
insert, err := db.Prepare(query) insert, err := db.Prepare(query)
defer insert.Close() defer insert.Close()
@ -30,7 +33,7 @@ func InsertCardPrinting(db *sql.DB, cardPrinting CardPrinting) error {
return err return err
} }
_, err = insert.Exec(cardPrinting.Name, cardPrinting.SetCode, cardPrinting.IsFoil, cardPrinting.IsPromo, cardPrinting.CollectorNumber, cardPrinting.Language) _, err = insert.Exec(cardPrinting.Id, cardPrinting.Name, cardPrinting.SetCode, cardPrinting.IsFoil, cardPrinting.IsPromo, cardPrinting.CollectorNumber, cardPrinting.ImageUrl, cardPrinting.Language)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,41 +1,42 @@
package database package database
import "database/sql" import (
"database/sql"
)
type CardStorageLocation struct { type CardLocation struct {
Id int Id int
CardPrintingId int CardPrintingId string
StorageBox string StorageAreaId int
Position int Position int
Source string
} }
func GetLastPositionInBox(db *sql.DB, storageBox string) (int, error) { func GetLastPositionInStorageArea(db *sql.DB, storageAreaId int) (int, error) {
query := "SELECT Position FROM CardStorageLocation WHERE StorageBox = ? ORDER BY Position DESC LIMIT 1" query := "SELECT Position FROM CardLocation WHERE StorageAreaId = ? ORDER BY Position DESC LIMIT 1;"
var lastPosition int var lastPosition int
err := db.QueryRow(query, storageBox).Scan(&lastPosition) err := db.QueryRow(query, storageAreaId).Scan(&lastPosition)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return 0, nil return 0, nil
} else { } else if err != nil {
return 0, err return 0, err
} }
return lastPosition, nil return lastPosition, nil
} }
func InsertCardStorageLocation(db *sql.DB, storageLocation CardStorageLocation) error { func InsertCardLocation(db *sql.DB, storageLocation CardLocation) error {
query := `INSERT INTO CardStorageLocation query := `INSERT INTO CardLocation
(CardPrintingId, StorageBox, Position, Source) (CardPrintingId, StorageAreaId, Position)
VALUES (?, ?, ?, ?);` VALUES (?, ?, ?);`
insert, err := db.Prepare(query) insert, err := db.Prepare(query)
if err != nil { if err != nil {
return err return err
} }
_, err = insert.Exec(storageLocation.CardPrintingId, storageLocation.StorageBox, storageLocation.Position, storageLocation.Source) _, err = insert.Exec(storageLocation.CardPrintingId, storageLocation.StorageAreaId, storageLocation.Position)
if err != nil { if err != nil {
return err return err
} }

View File

@ -0,0 +1,38 @@
package database
import "database/sql"
type LocateCardResult struct {
CardName string
SetCode string
IsFoil bool
IsPromo bool
CollectorNumber string
Language string
StorageAreaType string
StorageAreaName string
Position int
}
func GetLocateCardResultsByCardName(db *sql.DB, cardName string) ([]LocateCardResult, error) {
var results []LocateCardResult
query := "SELECT CardPrinting.Name, CardPrinting.SetCode, CardPrinting.IsFoil, CardPrinting.IsPromo, CardPrinting.CollectorNumber, CardPrinting.Language, StorageArea.StorageType, StorageArea.Name, CardLocation.Position FROM CardLocation JOIN CardPrinting ON CardLocation.CardPrintingId = CardPrinting.Id JOIN StorageArea ON CardLocation.StorageAreaId = StorageArea.Id WHERE CardPrinting.Name = ?;"
rows, err := db.Query(query, cardName)
defer rows.Close()
if err != nil {
return results, err
}
var result LocateCardResult
for rows.Next() {
err := rows.Scan(&result.CardName, &result.SetCode, &result.IsFoil, &result.IsPromo, &result.CollectorNumber, &result.Language, &result.StorageAreaType, &result.StorageAreaName, &result.Position)
if err != nil {
return results, err
}
results = append(results, result)
}
return results, nil
}

View File

@ -15,21 +15,28 @@ CREATE TABLE IF NOT EXISTS ExpansionSet (
); );
CREATE TABLE IF NOT EXISTS CardPrinting ( CREATE TABLE IF NOT EXISTS CardPrinting (
Id INT AUTO_INCREMENT PRIMARY KEY, Id VARCHAR(37) PRIMARY KEY, -- GUID, plus one character for foil/nonfoil
Name VARCHAR(150) NOT NULL, Name VARCHAR(150) NOT NULL,
SetCode VARCHAR(6) NOT NULL, SetCode VARCHAR(6) NOT NULL,
FOREIGN KEY (SetCode) REFERENCES ExpansionSet(SetCode), FOREIGN KEY (SetCode) REFERENCES ExpansionSet(SetCode),
IsFoil BOOLEAN NOT NULL, IsFoil BOOLEAN NOT NULL,
IsPromo BOOLEAN NOT NULL, IsPromo BOOLEAN NOT NULL,
CollectorNumber VARCHAR(10) NOT NULL, CollectorNumber VARCHAR(10) NOT NULL,
ImageUrl VARCHAR(100) NOT NULL,
Language VARCHAR(3) NOT NULL Language VARCHAR(3) NOT NULL
); );
CREATE TABLE IF NOT EXISTS CardStorageLocation ( CREATE TABLE IF NOT EXISTS StorageArea (
Id INT AUTO_INCREMENT PRIMARY KEY, Id INT AUTO_INCREMENT PRIMARY KEY,
CardPrintingId INT NOT NULL, Name VARCHAR(100) NOT NULL,
FOREIGN KEY (CardPrintingId) REFERENCES CardPrinting(Id), StorageType ENUM('Binder', 'Box')
StorageBox VARCHAR(20) NOT NULL, );
Position INT NOT NULL,
Source VARCHAR(100) NULL CREATE TABLE IF NOT EXISTS CardLocation (
Id INT AUTO_INCREMENT PRIMARY KEY,
CardPrintingId VARCHAR(37) NULL,
FOREIGN KEY (CardPrintingId) REFERENCES CardPrinting(Id),
StorageAreaId INT NOT NULL,
FOREIGN KEY (StorageAreaId) REFERENCES StorageArea(Id),
Position INT NULL
); );

View File

@ -0,0 +1,48 @@
package database
import "database/sql"
type StorageArea struct {
Id int
Name string
StorageType string
}
func GetAllStorageAreas(db *sql.DB) ([]StorageArea, error) {
var storageAreas []StorageArea
query := `SELECT * FROM StorageArea;`
rows, err := db.Query(query)
defer rows.Close()
if err != nil {
return storageAreas, err
}
var area StorageArea
for rows.Next() {
err := rows.Scan(&area.Id, &area.Name, &area.StorageType)
if err != nil {
return storageAreas, err
}
storageAreas = append(storageAreas, area)
}
return storageAreas, nil
}
func InsertStorageArea(db *sql.DB, storageArea StorageArea) error {
query := `INSERT INTO StorageArea (Name, StorageType) VALUES (?, ?);`
insert, err := db.Prepare(query)
if err != nil {
return err
}
_, err = insert.Exec(storageArea.Name, storageArea.StorageType)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,68 @@
package figlet
import (
"os"
"path/filepath"
"sevenkeys/logic"
"github.com/lukesampson/figlet/figletlib"
"golang.org/x/crypto/ssh/terminal"
)
var (
FigletFontSlant *figletlib.Font
FigletFontTerm *figletlib.Font
)
func ReadFigletFonts() {
cwd, err := os.Getwd()
logic.Check(err)
fontsdir := filepath.Join(cwd, "fonts")
FigletFontSlant, err = figletlib.GetFontByName(fontsdir, "slant")
logic.Check(err)
FigletFontTerm, err = figletlib.GetFontByName(fontsdir, "term")
logic.Check(err)
}
func getTerminalWidth() int {
width, _, err := terminal.GetSize(0)
if err != nil {
return 80
}
return width
}
func PrintMsgSlant(msg, alignment string) {
figletlib.PrintMsg(msg,
FigletFontSlant,
getTerminalWidth(),
FigletFontSlant.Settings(),
alignment)
}
func PrintMsgTerm(msg, alignment string) {
figletlib.PrintMsg(msg,
FigletFontTerm,
getTerminalWidth(),
FigletFontTerm.Settings(),
alignment)
}
func SprintMsgSlant(msg, alignment string) string {
return figletlib.SprintMsg(msg,
FigletFontSlant,
getTerminalWidth(),
FigletFontSlant.Settings(),
alignment)
}
func SprintMsgTerm(msg, alignment string) string {
return figletlib.SprintMsg(msg,
FigletFontTerm,
getTerminalWidth(),
FigletFontTerm.Settings(),
alignment)
}

2494
sevenkeys/fonts/banner.flf Normal file

File diff suppressed because it is too large Load Diff

2204
sevenkeys/fonts/big.flf Normal file

File diff suppressed because it is too large Load Diff

1691
sevenkeys/fonts/block.flf Normal file

File diff suppressed because it is too large Load Diff

1630
sevenkeys/fonts/bubble.flf Normal file

File diff suppressed because it is too large Load Diff

1286
sevenkeys/fonts/digital.flf Normal file

File diff suppressed because it is too large Load Diff

900
sevenkeys/fonts/ivrit.flf Normal file
View File

@ -0,0 +1,900 @@
flf2a$ 6 5 76 15 14 1 16271 39
Ivrit (Hebrew) Unicode font assembled by John Cowan <cowan@ccil.org>
Latin chars from Standard by G. Chappell & Ian Chai
Hebrew chars from Jerusalem by Gedaliah Friedenberg <gfrieden@nyx.cs.du.edu>
Use "ilhebrew.flc" for Hebrew keyboard mapping
Use "ushebrew.flc" for U.S.-style keyboard mapping ("febrew" script)
Use "8859-8.flc" for ISO 8859-8 text
Or use UTF-8
WARNING! FIGfonts aren't bidirectional; this is strictly right-to-left
(by default) even for the Latin characters.
figlet release 2.2 -- November 1996
Modified by Paul Burton <solution@earthlink.net> 12/96 to include new parameter
supported by FIGlet and FIGWin. May also be slightly modified for better use
of new full-width/kern/smush alternatives, but default output is NOT changed.
$@
$@
$@
$@
$@
$@@
_ @
| |@
| |@
|_|@
(_)@
@@
_ _ @
( | )@
V V @
$ @
$ @
@@
_ _ @
_| || |_ @
|_ .. _|@
|_ _|@
|_||_| @
@@
_ @
| | @
/ __)@
\__ \@
( /@
|_| @@
_ __@
(_)/ /@
/ / @
/ /_ @
/_/(_)@
@@
___ @
( _ ) @
/ _ \/\@
| (_> <@
\___/\/@
@@
_ @
( )@
|/ @
$ @
$ @
@@
__@
/ /@
| | @
| | @
| | @
\_\@@
__ @
\ \ @
| |@
| |@
| |@
/_/ @@
@
__/\__@
\ /@
/_ _\@
\/ @
@@
@
_ @
_| |_ @
|_ _|@
|_| @
@@
@
@
@
_ @
( )@
|/ @@
@
@
_____ @
|_____|@
$ @
@@
@
@
@
_ @
(_)@
@@
__@
/ /@
/ / @
/ / @
/_/ @
@@
___ @
/ _ \ @
| | | |@
| |_| |@
\___/ @
@@
_ @
/ |@
| |@
| |@
|_|@
@@
____ @
|___ \ @
__) |@
/ __/ @
|_____|@
@@
_____ @
|___ / @
|_ \ @
___) |@
|____/ @
@@
_ _ @
| || | @
| || |_ @
|__ _|@
|_| @
@@
____ @
| ___| @
|___ \ @
___) |@
|____/ @
@@
__ @
/ /_ @
| '_ \ @
| (_) |@
\___/ @
@@
_____ @
|___ |@
/ / @
/ / @
/_/ @
@@
___ @
( _ ) @
/ _ \ @
| (_) |@
\___/ @
@@
___ @
/ _ \ @
| (_) |@
\__, |@
/_/ @
@@
@
_ @
(_)@
_ @
(_)@
@@
@
_ @
(_)@
_ @
( )@
|/ @@
__@
/ /@
/ / @
\ \ @
\_\@
@@
@
_____ @
|_____|@
|_____|@
$ @
@@
__ @
\ \ @
\ \@
/ /@
/_/ @
@@
___ @
|__ \@
/ /@
|_| @
(_) @
@@
____ @
/ __ \ @
/ / _` |@
| | (_| |@
\ \__,_|@
\____/ @@
_ @
/ \ @
/ _ \ @
/ ___ \ @
/_/ \_\@
@@
____ @
| __ ) @
| _ \ @
| |_) |@
|____/ @
@@
____ @
/ ___|@
| | @
| |___ @
\____|@
@@
____ @
| _ \ @
| | | |@
| |_| |@
|____/ @
@@
_____ @
| ____|@
| _| @
| |___ @
|_____|@
@@
_____ @
| ___|@
| |_ @
| _| @
|_| @
@@
____ @
/ ___|@
| | _ @
| |_| |@
\____|@
@@
_ _ @
| | | |@
| |_| |@
| _ |@
|_| |_|@
@@
___ @
|_ _|@
| | @
| | @
|___|@
@@
_ @
| |@
_ | |@
| |_| |@
\___/ @
@@
_ __@
| |/ /@
| ' / @
| . \ @
|_|\_\@
@@
_ @
| | @
| | @
| |___ @
|_____|@
@@
__ __ @
| \/ |@
| |\/| |@
| | | |@
|_| |_|@
@@
_ _ @
| \ | |@
| \| |@
| |\ |@
|_| \_|@
@@
___ @
/ _ \ @
| | | |@
| |_| |@
\___/ @
@@
____ @
| _ \ @
| |_) |@
| __/ @
|_| @
@@
___ @
/ _ \ @
| | | |@
| |_| |@
\__\_\@
@@
____ @
| _ \ @
| |_) |@
| _ < @
|_| \_\@
@@
____ @
/ ___| @
\___ \ @
___) |@
|____/ @
@@
_____ @
|_ _|@
| | @
| | @
|_| @
@@
_ _ @
| | | |@
| | | |@
| |_| |@
\___/ @
@@
__ __@
\ \ / /@
\ \ / / @
\ V / @
\_/ @
@@
__ __@
\ \ / /@
\ \ /\ / / @
\ V V / @
\_/\_/ @
@@
__ __@
\ \/ /@
\ / @
/ \ @
/_/\_\@
@@
__ __@
\ \ / /@
\ V / @
| | @
|_| @
@@
_____@
|__ /@
/ / @
/ /_ @
/____|@
@@
__ @
| _|@
| | @
| | @
| | @
|__|@@
__ @
\ \ @
\ \ @
\ \ @
\_\@
@@
__ @
|_ |@
| |@
| |@
| |@
|__|@@
/\ @
|/\|@
$ @
$ @
$ @
@@
@
@
@
@
_____ @
|_____|@@
_ @
( )@
\|@
$ @
$ @
@@
@
__ _ @
/ _` |@
| (_| |@
\__,_|@
@@
_ @
| |__ @
| '_ \ @
| |_) |@
|_.__/ @
@@
@
___ @
/ __|@
| (__ @
\___|@
@@
_ @
__| |@
/ _` |@
| (_| |@
\__,_|@
@@
@
___ @
/ _ \@
| __/@
\___|@
@@
__ @
/ _|@
| |_ @
| _|@
|_| @
@@
@
__ _ @
/ _` |@
| (_| |@
\__, |@
|___/ @@
_ @
| |__ @
| '_ \ @
| | | |@
|_| |_|@
@@
_ @
(_)@
| |@
| |@
|_|@
@@
_ @
(_)@
| |@
| |@
_/ |@
|__/ @@
_ @
| | __@
| |/ /@
| < @
|_|\_\@
@@
_ @
| |@
| |@
| |@
|_|@
@@
@
_ __ ___ @
| '_ ` _ \ @
| | | | | |@
|_| |_| |_|@
@@
@
_ __ @
| '_ \ @
| | | |@
|_| |_|@
@@
@
___ @
/ _ \ @
| (_) |@
\___/ @
@@
@
_ __ @
| '_ \ @
| |_) |@
| .__/ @
|_| @@
@
__ _ @
/ _` |@
| (_| |@
\__, |@
|_|@@
@
_ __ @
| '__|@
| | @
|_| @
@@
@
___ @
/ __|@
\__ \@
|___/@
@@
_ @
| |_ @
| __|@
| |_ @
\__|@
@@
@
_ _ @
| | | |@
| |_| |@
\__,_|@
@@
@
__ __@
\ \ / /@
\ V / @
\_/ @
@@
@
__ __@
\ \ /\ / /@
\ V V / @
\_/\_/ @
@@
@
__ __@
\ \/ /@
> < @
/_/\_\@
@@
@
_ _ @
| | | |@
| |_| |@
\__, |@
|___/ @@
@
____@
|_ /@
/ / @
/___|@
@@
__@
/ /@
| | @
< < @
| | @
\_\@@
_ @
| |@
| |@
| |@
| |@
|_|@@
__ @
\ \ @
| | @
> >@
| | @
/_/ @@
/\/|@
|/\/ @
$ @
$ @
$ @
@@
_ _ @
(_)_(_)@
/_\ @
/ _ \ @
/_/ \_\@
@@
_ _ @
(_)_(_)@
/ _ \ @
| |_| |@
\___/ @
@@
_ _ @
(_) (_)@
| | | |@
| |_| |@
\___/ @
@@
_ _ @
(_)_(_)@
/ _` |@
| (_| |@
\__,_|@
@@
_ _ @
(_)_(_)@
/ _ \ @
| (_) |@
\___/ @
@@
_ _ @
(_) (_)@
| | | |@
| |_| |@
\__,_|@
@@
___ @
/ _ \@
| |/ /@
| |\ \@
| ||_/@
|_| @@
160 NO-BREAK SPACE
$@
$@
$@
$@
$@
$@@
173 SOFT HYPHEN
@
@
_____ @
|_____|@
$ @
@@
196 LATIN CAPITAL LETTER A WITH DIAERESIS
_ _ @
(_)_(_)@
/_\ @
/ _ \ @
/_/ \_\@
@@
214 LATIN CAPITAL LETTER O WITH DIAERESIS
_ _ @
(_)_(_)@
/ _ \ @
| |_| |@
\___/ @
@@
220 LATIN CAPITAL LETTER U WITH DIAERESIS
_ _ @
(_) (_)@
| | | |@
| |_| |@
\___/ @
@@
223 LATIN SMALL LETTER SHARP S
___ @
/ _ \@
| |/ /@
| |\ \@
| ||_/@
|_| @@
228 LATIN SMALL LETTER A WITH DIAERESIS
_ _ @
(_)_(_)@
/ _` |@
| (_| |@
\__,_|@
@@
246 LATIN SMALL LETTER O WITH DIAERESIS
_ _ @
(_)_(_)@
/ _ \ @
| (_) |@
\___/ @
@@
252 LATIN SMALL LETTER U WITH DIAERESIS
_ _ @
(_) (_)@
| | | |@
| |_| |@
\__,_|@
@@
0x05D0 HEBREW LETTER ALEF
__ __@
\ \ / /@
| V / @
| |\ \ @
|_| \_\@
@@
0x05D1 HEBREW LETTER BET
______ @
|____ | @
| | @
_____| |_@
/________/@
@@
0x05D2 HEBREW LETTER GIMEL
____ @
|__ | @
| | @
____| | @
/____/\_\@
@@
0x05D3 HEBREW LETTER DALET
_______ @
|____ |@
| | @
| | @
|_| @
@@
0x05D4 HEBREW LETTER HE
_______ @
|_____ |@
_ | |@
| | | |@
|_| |_|@
@@
0x05D5 HEBREW LETTER VAV
___ @
|_ |@
| |@
| |@
|_|@
@@
0x05D6 HEBREW LETTER ZAYIN
________ @
\__ __\@
| | @
| | @
|_| @
@@
0x05D7 HEBREW LETTER HET
_______ @
|. __ |@
| | | |@
| | | |@
|_| |_|@
@@
0x05D8 HEBREW LETTER TET
__ ___ @
|. | /_ |@
| | | |@
| |___| |@
|_______|@
@@
0x05D9 HEBREW LETTER YOD
___ @
|_ |@
|_|@
$ @
$ @
@@
0x05DA HEBREW LETTER FINAL KAF
_______ @
|____ .|@
| | @
| | @
| | @
|_| @@
0x05DB HEBREW LETTER KAF
_____ @
|____ \ @
| |@
____| |@
|_____/ @
@@
0x05DC HEBREW LETTER LAMED
|=|____ @
|____ |@
/ / @
/ / @
/_/ @
@@
0x05DD HEBREW LETTER FINAL MEM
________ @
|. ___ |@
| | | |@
| |___| |@
|_______|@
@@
0x05DE HEBREW LETTER MEM
_______ @
|. __ |@
| | | |@
| | _| |@
|_||___|@
@@
0x05DF HEBREW LETTER FINAL NUN
___ @
|_ |@
| |@
| |@
| |@
|_|@@
0x05E0 HEBREW LETTER NUN
___ @
|_ |@
| |@
__| |@
|____|@
@@
0x05E1 HEBREW LETTER SAMEKH
_______ @
|. __ |@
| | | |@
| |__/ |@
|_____/ @
@@
0x05E2 HEBREW LETTER AYIN
__ _ @
\ \ | |@
\ \| |@
__\ ` |@
|______|@
@@
0x05E3 HEBREW LETTER FINAL PE
______ @
| __ |@
| |_ | |@
|___|| |@
| |@
|_|@@
0x05E4 HEBREW LETTER PE
_______ @
| ___ |@
\_\ | |@
_____| |@
|_______|@
@@
0x05E5 HEBREW LETTER FINAL TSADI
__ _ @
|. | | |@
| | // @
| |// @
| | @
|_| @@
0x05E6 HEBREW LETTER TSADI
__ __.@
\ \ / / @
\ V / @
___\ \ @
|______| @
@@
0x05E7 HEBREW LETTER QOF
______ @
|____ |@
_ | |@
| | |_|@
| | @
|_| @@
0x05E8 HEBREW LETTER RESH
______ @
|____ |@
| |@
| |@
|_|@
@@
0x05E9 HEBREW LETTER SHIN
_ _ _ @
| | | | | |@
| | | | | |@
| |/ /_/ / @
|_______/ @
@@
0x05EA HEBREW LETTER TAV
______ @
| __ |@
| | | |@
_| | | |@
|___| |_|@
@@
0x2721 STAR OF DAVID
@
__/\__@
\ /@
/_ _\@
\/ @
@@
-0x0002
aleph = t, bet/vet = c, gimel = d, dalet = s, he = v, vav = u, zayin = z @
het = j, tet = y, yod = h, kaf/chaf = f, final kaf = l, lamed = k, mem = n@
final mem = o, nun = b, final nun = i, samekh = x, ayin = g, pe/fe = p, @
final pe = ;, tsadi = m, final tsadi = ., qof = e, resh = r, shin/sin = a @
tav = , comma = ', period = /, semicolon = `, slash = q, apostrophe = w @
Star of David = * @@
-0x0003
aleph = a, bet/vet = b, gimel = g, dalet = d, he = h, vav = v, zayin = z @
het = c, tet = t, yod = y, kaf/chaf = k, final kaf = f, lamed = l, mem = m@
final mem = o, nun = n, final nun = i, samekh = e, ayin = _, pe/fe = p, @
final pe = u, tsadi = j, final tsadi = w, qof = q, resh = r, shin/sin = s @
tav = x @
Star of David = * @@

1691
sevenkeys/fonts/lean.flf Normal file

File diff suppressed because it is too large Load Diff

899
sevenkeys/fonts/mini.flf Normal file
View File

@ -0,0 +1,899 @@
flf2a$ 4 3 10 0 10 0 1920 96
Mini by Glenn Chappell 4/93
Includes ISO Latin-1
figlet release 2.1 -- 12 Aug 1994
Permission is hereby given to modify this font, as long as the
modifier's name is placed on a comment line.
Modified by Paul Burton <solution@earthlink.net> 12/96 to include new parameter
supported by FIGlet and FIGWin. May also be slightly modified for better use
of new full-width/kern/smush alternatives, but default output is NOT changed.
$$@
$$@
$$@
$$@@
@
|$@
o$@
@@
@
||$@
@
@@
@
-|-|-$@
-|-|-$@
@@
_$@
(|$ @
_|)$@
@@
@
O/$@
/O$@
@@
@
()$ @
(_X$@
@@
@
/$@
@
@@
@
/$@
|$ @
\$@@
@
\$ @
|$@
/$ @@
@
\|/$@
/|\$@
@@
@
_|_$@
|$ @
@@
@
@
o$@
/$@@
@
__$@
@
@@
@
@
o$@
@@
@
/$@
/$ @
@@
_$ @
/ \$@
\_/$@
@@
@
/|$@
|$@
@@
_$ @
)$@
/_$@
@@
_$ @
_)$@
_)$@
@@
@
|_|_$@
|$ @
@@
_$ @
|_$ @
_)$@
@@
_$ @
|_$ @
|_)$@
@@
__$@
/$@
/$ @
@@
_$ @
(_)$@
(_)$@
@@
_$ @
(_|$@
|$@
@@
@
o$@
o$@
@@
@
o$@
o$@
/$@@
@
/$@
\$@
@@
@
--$@
--$@
@@
@
\$@
/$@
@@
_$ @
)$@
o$ @
@@
__$ @
/ \$@
| (|/$@
\__$ @@
@
/\$ @
/--\$@
@@
_$ @
|_)$@
|_)$@
@@
_$@
/$ @
\_$@
@@
_$ @
| \$@
|_/$@
@@
_$@
|_$@
|_$@
@@
_$@
|_$@
|$ @
@@
__$@
/__$@
\_|$@
@@
@
|_|$@
| |$@
@@
___$@
|$ @
_|_$@
@@
@
|$@
\_|$@
@@
@
|/$@
|\$@
@@
@
|$ @
|_$@
@@
@
|\/|$@
| |$@
@@
@
|\ |$@
| \|$@
@@
_$ @
/ \$@
\_/$@
@@
_$ @
|_)$@
|$ @
@@
_$ @
/ \$@
\_X$@
@@
_$ @
|_)$@
| \$@
@@
__$@
(_$ @
__)$@
@@
___$@
|$ @
|$ @
@@
@
| |$@
|_|$@
@@
@
\ /$@
\/$ @
@@
@
\ /$@
\/\/$ @
@@
@
\/$@
/\$@
@@
@
\_/$@
|$ @
@@
__$@
/$@
/_$@
@@
_$@
|$ @
|_$@
@@
@
\$ @
\$@
@@
_$ @
|$@
_|$@
@@
/\$@
@
@
@@
@
@
@
__$@@
@
\$@
@
@@
@
_.$@
(_|$@
@@
@
|_$ @
|_)$@
@@
@
_$@
(_$@
@@
@
_|$@
(_|$@
@@
@
_$ @
(/_$@
@@
_$@
_|_$@
|$ @
@@
@
_$ @
(_|$@
_|$@@
@
|_$ @
| |$@
@@
@
o$@
|$@
@@
@
o$@
|$@
_|$@@
@
|$ @
|<$@
@@
@
|$@
|$@
@@
@
._ _$ @
| | |$@
@@
@
._$ @
| |$@
@@
@
_$ @
(_)$@
@@
@
._$ @
|_)$@
|$ @@
@
_.$@
(_|$@
|$@@
@
._$@
|$ @
@@
@
_$@
_>$@
@@
@
_|_$@
|_$@
@@
@
@
|_|$@
@@
@
@
\/$@
@@
@
@
\/\/$@
@@
@
@
><$@
@@
@
@
\/$@
/$ @@
@
_$ @
/_$@
@@
,-$@
_|$ @
|$ @
`-$@@
|$@
|$@
|$@
|$@@
-.$ @
|_$@
|$ @
-'$ @@
/\/$@
@
@
@@
o o$@
/\$ @
/--\$@
@@
o_o$@
/ \$@
\_/$@
@@
o o$@
| |$@
|_|$@
@@
o o$@
_.$@
(_|$@
@@
o o$@
_$ @
(_)$@
@@
o o$@
@
|_|$@
@@
_$ @
| )$@
| )$@
|$ @@
160 NO-BREAK SPACE
$$@
$$@
$$@
$$@@
161 INVERTED EXCLAMATION MARK
@
o$@
|$@
@@
162 CENT SIGN
@
|_$@
(__$@
|$ @@
163 POUND SIGN
_$ @
_/_`$ @
|___$@
@@
164 CURRENCY SIGN
@
`o'$@
' `$@
@@
165 YEN SIGN
@
_\_/_$@
--|--$@
@@
166 BROKEN BAR
|$@
|$@
|$@
|$@@
167 SECTION SIGN
_$@
($ @
()$@
_)$@@
168 DIAERESIS
o o$@
@
@
@@
169 COPYRIGHT SIGN
_$ @
|C|$@
`-'$@
@@
170 FEMININE ORDINAL INDICATOR
_.$@
(_|$@
---$@
@@
171 LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
@
//$@
\\$@
@@
172 NOT SIGN
@
__$ @
|$@
@@
173 SOFT HYPHEN
@
_$@
@
@@
174 REGISTERED SIGN
_$ @
|R|$@
`-'$@
@@
175 MACRON
__$@
@
@
@@
176 DEGREE SIGN
O$@
@
@
@@
177 PLUS-MINUS SIGN
@
_|_$@
_|_$@
@@
178 SUPERSCRIPT TWO
2$@
@
@
@@
179 SUPERSCRIPT THREE
3$@
@
@
@@
180 ACUTE ACCENT
/$@
@
@
@@
181 MICRO SIGN
@
@
|_|$@
|$ @@
182 PILCROW SIGN
__$ @
(| |$@
| |$@
@@
183 MIDDLE DOT
@
o$@
@
@@
184 CEDILLA
@
@
@
S$@@
185 SUPERSCRIPT ONE
1$@
@
@
@@
186 MASCULINE ORDINAL INDICATOR
_$ @
(_)$@
---$@
@@
187 RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
@
\\$@
//$@
@@
188 VULGAR FRACTION ONE QUARTER
@
1/$@
/4$@
@@
189 VULGAR FRACTION ONE HALF
@
1/$@
/2$@
@@
190 VULGAR FRACTION THREE QUARTERS
@
3/$@
/4$@
@@
191 INVERTED QUESTION MARK
@
o$@
(_$@
@@
192 LATIN CAPITAL LETTER A WITH GRAVE
\$ @
/\$ @
/--\$@
@@
193 LATIN CAPITAL LETTER A WITH ACUTE
/$ @
/\$ @
/--\$@
@@
194 LATIN CAPITAL LETTER A WITH CIRCUMFLEX
/\$ @
/\$ @
/--\$@
@@
195 LATIN CAPITAL LETTER A WITH TILDE
/\/$@
/\$ @
/--\$@
@@
196 LATIN CAPITAL LETTER A WITH DIAERESIS
o o$@
/\$ @
/--\$@
@@
197 LATIN CAPITAL LETTER A WITH RING ABOVE
O$ @
/ \$ @
/---\$@
@@
198 LATIN CAPITAL LETTER AE
_$@
/|_$@
/-|_$@
@@
199 LATIN CAPITAL LETTER C WITH CEDILLA
_$@
/$ @
\_$@
S$@@
200 LATIN CAPITAL LETTER E WITH GRAVE
\_$@
|_$@
|_$@
@@
201 LATIN CAPITAL LETTER E WITH ACUTE
_/$@
|_$ @
|_$ @
@@
202 LATIN CAPITAL LETTER E WITH CIRCUMFLEX
/\$@
|_$ @
|_$ @
@@
203 LATIN CAPITAL LETTER E WITH DIAERESIS
o_o$@
|_$ @
|_$ @
@@
204 LATIN CAPITAL LETTER I WITH GRAVE
\__$@
|$ @
_|_$@
@@
205 LATIN CAPITAL LETTER I WITH ACUTE
__/$@
|$ @
_|_$@
@@
206 LATIN CAPITAL LETTER I WITH CIRCUMFLEX
/\$@
___$@
_|_$@
@@
207 LATIN CAPITAL LETTER I WITH DIAERESIS
o_o$@
|$ @
_|_$@
@@
208 LATIN CAPITAL LETTER ETH
_$ @
_|_\$@
|_/$@
@@
209 LATIN CAPITAL LETTER N WITH TILDE
/\/$@
|\ |$@
| \|$@
@@
210 LATIN CAPITAL LETTER O WITH GRAVE
\$ @
/ \$@
\_/$@
@@
211 LATIN CAPITAL LETTER O WITH ACUTE
/$ @
/ \$@
\_/$@
@@
212 LATIN CAPITAL LETTER O WITH CIRCUMFLEX
/\$@
/ \$@
\_/$@
@@
213 LATIN CAPITAL LETTER O WITH TILDE
/\/$@
/ \$@
\_/$@
@@
214 LATIN CAPITAL LETTER O WITH DIAERESIS
o_o$@
/ \$@
\_/$@
@@
215 MULTIPLICATION SIGN
@
@
X$@
@@
216 LATIN CAPITAL LETTER O WITH STROKE
__$ @
/ /\$@
\/_/$@
@@
217 LATIN CAPITAL LETTER U WITH GRAVE
\$ @
| |$@
|_|$@
@@
218 LATIN CAPITAL LETTER U WITH ACUTE
/$ @
| |$@
|_|$@
@@
219 LATIN CAPITAL LETTER U WITH CIRCUMFLEX
/\$@
| |$@
|_|$@
@@
220 LATIN CAPITAL LETTER U WITH DIAERESIS
o o$@
| |$@
|_|$@
@@
221 LATIN CAPITAL LETTER Y WITH ACUTE
/$ @
\_/$@
|$ @
@@
222 LATIN CAPITAL LETTER THORN
|_$ @
|_)$@
|$ @
@@
223 LATIN SMALL LETTER SHARP S
_$ @
| )$@
| )$@
|$ @@
224 LATIN SMALL LETTER A WITH GRAVE
\$ @
_.$@
(_|$@
@@
225 LATIN SMALL LETTER A WITH ACUTE
/$ @
_.$@
(_|$@
@@
226 LATIN SMALL LETTER A WITH CIRCUMFLEX
/\$@
_.$@
(_|$@
@@
227 LATIN SMALL LETTER A WITH TILDE
/\/$@
_.$@
(_|$@
@@
228 LATIN SMALL LETTER A WITH DIAERESIS
o o$@
_.$@
(_|$@
@@
229 LATIN SMALL LETTER A WITH RING ABOVE
O$ @
_.$@
(_|$@
@@
230 LATIN SMALL LETTER AE
@
___$ @
(_|/_$@
@@
231 LATIN SMALL LETTER C WITH CEDILLA
@
_$@
(_$@
S$@@
232 LATIN SMALL LETTER E WITH GRAVE
\$ @
_$ @
(/_$@
@@
233 LATIN SMALL LETTER E WITH ACUTE
/$ @
_$ @
(/_$@
@@
234 LATIN SMALL LETTER E WITH CIRCUMFLEX
/\$@
_$ @
(/_$@
@@
235 LATIN SMALL LETTER E WITH DIAERESIS
o o$@
_$ @
(/_$@
@@
236 LATIN SMALL LETTER I WITH GRAVE
\$@
@
|$@
@@
237 LATIN SMALL LETTER I WITH ACUTE
/$@
@
|$@
@@
238 LATIN SMALL LETTER I WITH CIRCUMFLEX
/\$@
@
|$ @
@@
239 LATIN SMALL LETTER I WITH DIAERESIS
o o$@
@
|$ @
@@
240 LATIN SMALL LETTER ETH
X$ @
\$ @
(_|$@
@@
241 LATIN SMALL LETTER N WITH TILDE
/\/$@
._$ @
| |$@
@@
242 LATIN SMALL LETTER O WITH GRAVE
\$ @
_$ @
(_)$@
@@
243 LATIN SMALL LETTER O WITH ACUTE
/$ @
_$ @
(_)$@
@@
244 LATIN SMALL LETTER O WITH CIRCUMFLEX
/\$@
_$ @
(_)$@
@@
245 LATIN SMALL LETTER O WITH TILDE
/\/$@
_$ @
(_)$@
@@
246 LATIN SMALL LETTER O WITH DIAERESIS
o o$@
_$ @
(_)$@
@@
247 DIVISION SIGN
o$ @
---$@
o$ @
@@
248 LATIN SMALL LETTER O WITH STROKE
@
_$ @
(/)$@
@@
249 LATIN SMALL LETTER U WITH GRAVE
\$ @
@
|_|$@
@@
250 LATIN SMALL LETTER U WITH ACUTE
/$ @
@
|_|$@
@@
251 LATIN SMALL LETTER U WITH CIRCUMFLEX
/\$@
@
|_|$@
@@
252 LATIN SMALL LETTER U WITH DIAERESIS
o o$@
@
|_|$@
@@
253 LATIN SMALL LETTER Y WITH ACUTE
/$@
@
\/$@
/$ @@
254 LATIN SMALL LETTER THORN
@
|_$ @
|_)$@
|$ @@
255 LATIN SMALL LETTER Y WITH DIAERESIS
oo$@
@
\/$@
/$ @@

3702
sevenkeys/fonts/mnemonic.flf Normal file

File diff suppressed because it is too large Load Diff

1493
sevenkeys/fonts/script.flf Normal file

File diff suppressed because it is too large Load Diff

1097
sevenkeys/fonts/shadow.flf Normal file

File diff suppressed because it is too large Load Diff

1295
sevenkeys/fonts/slant.flf Normal file

File diff suppressed because it is too large Load Diff

1097
sevenkeys/fonts/small.flf Normal file

File diff suppressed because it is too large Load Diff

1097
sevenkeys/fonts/smscript.flf Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,899 @@
flf2a$ 4 3 14 0 10 0 1920 96
SmShadow by Glenn Chappell 4/93 -- based on Small
Includes ISO Latin-1
figlet release 2.1 -- 12 Aug 1994
Permission is hereby given to modify this font, as long as the
modifier's name is placed on a comment line.
Modified by Paul Burton <solution@earthlink.net> 12/96 to include new parameter
supported by FIGlet and FIGWin. May also be slightly modified for better use
of new full-width/kern/smush alternatives, but default output is NOT changed.
$$@
$$@
$$@
$$@@
|$@
_|$@
_)$@
@@
| )$@
V V$ @
@
@@
| |$ @
_ |_ |_|$@
_ |_ |_|$@
_| _|$ @@
|$ @
(_-<$@
_ _/$@
_|$ @@
_) /$ @
/$ @
_/ _)$@
@@
_|$ @
_| _|$@
\____|$ @
@@
)$@
/$ @
@
@@
/$@
|$ @
|$ @
\_\$@@
\ \$ @
|$@
|$@
_/$ @@
\ \ /$ @
_ _|$@
_/ _\$ @
@@
|$ @
__ __|$@
_|$ @
@@
@
@
)$@
/$ @@
@
____|$@
@
@@
@
@
_)$@
@@
/$@
/$ @
_/$ @
@@
\$ @
( |$@
\__/$ @
@@
_ |$@
|$@
_|$@
@@
_ )$@
/$ @
___|$@
@@
__ /$@
_ \$@
___/$@
@@
| |$ @
__ _|$@
_|$ @
@@
__|$@
__ \$@
___/$@
@@
/$ @
_ \$@
\___/$@
@@
__ /$@
/$ @
_/$ @
@@
_ )$@
_ \$@
\___/$@
@@
_ \$@
\_ /$@
_/$ @
@@
_)$@
@
_)$@
@@
_)$@
@
)$@
/$ @@
/$@
< <$ @
\_\$@
@@
@
____|$@
____|$@
@@
\ \$ @
> >$@
_/$ @
@@
__ \$@
_/$@
_)$ @
@@
__ \$ @
/ _` |$@
\__,_|$@
\____/$ @@
\$ @
_ \$ @
_/ _\$@
@@
_ )$@
_ \$@
___/$@
@@
__|$@
($ @
\___|$@
@@
_ \$ @
| |$@
___/$ @
@@
__|$@
_|$ @
___|$@
@@
__|$@
_|$ @
_|$ @
@@
__|$@
(_ |$@
\___|$@
@@
| |$@
__ |$@
_| _|$@
@@
_ _|$@
|$ @
___|$@
@@
|$@
\ |$@
\__/$ @
@@
| /$@
. <$ @
_|\_\$@
@@
|$ @
|$ @
____|$@
@@
\ |$@
|\/ |$@
_| _|$@
@@
\ |$@
. |$@
_|\_|$@
@@
_ \$ @
( |$@
\___/$ @
@@
_ \$@
__/$@
_|$ @
@@
_ \$ @
( |$@
\__\_\$@
@@
_ \$@
/$@
_|_\$@
@@
__|$@
\__ \$@
____/$@
@@
__ __|$@
|$ @
_|$ @
@@
| |$@
| |$@
\__/$ @
@@
\ \ /$@
\ \ /$ @
\_/$ @
@@
\ \ /$@
\ \ \ /$ @
\_/\_/$ @
@@
\ \ /$@
> <$ @
_/\_\$@
@@
\ \ /$@
\ /$ @
_|$ @
@@
__ /$@
/$ @
____|$@
@@
_|$@
|$ @
|$ @
__|$@@
\ \$ @
\ \$ @
\_\$@
@@
_ |$@
|$@
|$@
__|$@@
\$ @
/\|$@
@
@@
@
@
@
____|$@@
)$@
\|$@
@
@@
@
_` |$@
\__,_|$@
@@
|$ @
_ \$@
_.__/$@
@@
@
_|$@
\__|$@
@@
|$@
_` |$@
\__,_|$@
@@
@
-_)$@
\___|$@
@@
_|$@
_|$@
_|$ @
@@
@
_` |$@
\__, |$@
____/$ @@
|$ @
\$ @
_| _|$@
@@
_)$@
|$@
_|$@
@@
_)$@
|$@
|$@
__/$ @@
|$ @
| /$@
_\_\$@
@@
|$@
|$@
_|$@
@@
@
` \$ @
_|_|_|$@
@@
@
\$ @
_| _|$@
@@
@
_ \$@
\___/$@
@@
@
_ \$@
.__/$@
_|$ @@
@
_` |$@
\__, |$@
_|$@@
@
_|$@
_|$ @
@@
@
(_-<$@
___/$@
@@
|$ @
_|$@
\__|$@
@@
@
| |$@
\_,_|$@
@@
@
\ \ /$@
\_/$ @
@@
@
\ \ \ /$@
\_/\_/$ @
@@
@
\ \ /$@
_\_\$@
@@
@
| |$@
\_, |$@
___/$ @@
@
_ /$@
___|$@
@@
/$@
_ |$ @
|$ @
\_\$@@
|$@
|$@
|$@
_|$@@
\ \$ @
|_$@
|$ @
_/$ @@
\ |$@
/\/$ @
@
@@
_) \_)$@
_ \$ @
/ _\$@
@@
_) _)$@
__ \$@
\____/$@
@@
_) _)$@
| |$@
\__/$ @
@@
_) _)$@
_` |$@
\__,_|$@
@@
_) _)$@
_ \$@
\___/$@
@@
_) _)$@
| |$@
\_,_|$@
@@
_ \$@
|< <$@
|__/$@
_|$ @@
160 NO-BREAK SPACE
$$@
$$@
$$@
$$@@
161 INVERTED EXCLAMATION MARK
_)$@
|$@
_|$@
@@
162 CENT SIGN
|$ @
_)$@
\ _)$@
|$ @@
163 POUND SIGN
_\$ @
_ _|$ @
_,___|$@
@@
164 CURRENCY SIGN
\ . /$@
_ \$@
\/ /$@
@@
165 YEN SIGN
\ \ /$ @
__ __|$@
__ __|$@
_|$ @@
166 BROKEN BAR
|$@
_|$@
|$@
_|$@@
167 SECTION SIGN
_)$@
\ \$ @
\ \/$ @
__/$ @@
168 DIAERESIS
_) _)$@
@
@
@@
169 COPYRIGHT SIGN
\$ @
_| \$@
\ \__| /$@
\____/$ @@
170 FEMININE ORDINAL INDICATOR
_` |$@
\__,_|$@
_____|$@
@@
171 LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
/ /$@
< < <$ @
\_\_\$@
@@
172 NOT SIGN
____ |$@
_|$@
@
@@
173 SOFT HYPHEN
@
___|$@
@
@@
174 REGISTERED SIGN
\$ @
-) \$@
\ |\\ /$@
\____/$ @@
175 MACRON
___|$@
@
@
@@
176 DEGREE SIGN
.\$@
\_/$@
@
@@
177 PLUS-MINUS SIGN
|$ @
_ _|$@
_|$ @
_____|$@@
178 SUPERSCRIPT TWO
_ )$@
__|$@
@
@@
179 SUPERSCRIPT THREE
_ /$@
__)$@
@
@@
180 ACUTE ACCENT
_/$@
@
@
@@
181 MICRO SIGN
@
| |$@
.,_|$@
_|$ @@
182 PILCROW SIGN
|$@
\_ | |$@
_|_|$@
@@
183 MIDDLE DOT
@
_)$@
@
@@
184 CEDILLA
@
@
@
_)$@@
185 SUPERSCRIPT ONE
_ |$@
_|$@
@
@@
186 MASCULINE ORDINAL INDICATOR
_ \$@
\___/$@
____|$@
@@
187 RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
\ \ \$ @
> > >$@
_/_/$ @
@@
188 VULGAR FRACTION ONE QUARTER
_ | /$ @
_| /_' |$@
_/ _|$@
@@
189 VULGAR FRACTION ONE HALF
_ | /$ @
_| /_ )$@
_/ __|$@
@@
190 VULGAR FRACTION THREE QUARTERS
_ / /$ @
__) /_' |$@
_/ _|$@
@@
191 INVERTED QUESTION MARK
_)$ @
/$ @
\___|$@
@@
192 LATIN CAPITAL LETTER A WITH GRAVE
\_\$ @
--\$ @
_/\_\$@
@@
193 LATIN CAPITAL LETTER A WITH ACUTE
_/$ @
--\$ @
_/\_\$@
@@
194 LATIN CAPITAL LETTER A WITH CIRCUMFLEX
/\\$ @
--\$ @
_/\_\$@
@@
195 LATIN CAPITAL LETTER A WITH TILDE
/ _/$ @
--\$ @
_/\_\$@
@@
196 LATIN CAPITAL LETTER A WITH DIAERESIS
_) \_)$@
_ \$ @
/ _\$@
@@
197 LATIN CAPITAL LETTER A WITH RING ABOVE
( )$ @
_ \$ @
_/ _\$@
@@
198 LATIN CAPITAL LETTER AE
, __|$@
_ _|$ @
_/ ___|$@
@@
199 LATIN CAPITAL LETTER C WITH CEDILLA
|$@
($ @
\___|$@
_)$ @@
200 LATIN CAPITAL LETTER E WITH GRAVE
\_\$@
-<$@
__<$@
@@
201 LATIN CAPITAL LETTER E WITH ACUTE
_/$@
-<$@
__<$@
@@
202 LATIN CAPITAL LETTER E WITH CIRCUMFLEX
/\\$@
-<$@
__<$@
@@
203 LATIN CAPITAL LETTER E WITH DIAERESIS
_) _)$@
-<$ @
__<$ @
@@
204 LATIN CAPITAL LETTER I WITH GRAVE
\_\$ @
_ _|$@
___|$@
@@
205 LATIN CAPITAL LETTER I WITH ACUTE
_/$ @
_ _|$@
___|$@
@@
206 LATIN CAPITAL LETTER I WITH CIRCUMFLEX
/\\$ @
_ _|$@
___|$@
@@
207 LATIN CAPITAL LETTER I WITH DIAERESIS
_) _)$@
_ _|$ @
___|$ @
@@
208 LATIN CAPITAL LETTER ETH
_ \$ @
_ _| |$@
___/$ @
@@
209 LATIN CAPITAL LETTER N WITH TILDE
/ _/$@
\ |$@
_|\_|$@
@@
210 LATIN CAPITAL LETTER O WITH GRAVE
\_\$ @
__ \$@
\____/$@
@@
211 LATIN CAPITAL LETTER O WITH ACUTE
_/$ @
__ \$@
\____/$@
@@
212 LATIN CAPITAL LETTER O WITH CIRCUMFLEX
/\\$ @
__ \$@
\____/$@
@@
213 LATIN CAPITAL LETTER O WITH TILDE
/ _/$ @
__ \$@
\____/$@
@@
214 LATIN CAPITAL LETTER O WITH DIAERESIS
_) _)$@
__ \$@
\____/$@
@@
215 MULTIPLICATION SIGN
\ \$@
, '$@
\/\/$@
@@
216 LATIN CAPITAL LETTER O WITH STROKE
_ /\$ @
( / |$@
\_/__/$ @
@@
217 LATIN CAPITAL LETTER U WITH GRAVE
\_\$ @
| |$@
\__/$ @
@@
218 LATIN CAPITAL LETTER U WITH ACUTE
_/$ @
| |$@
\__/$ @
@@
219 LATIN CAPITAL LETTER U WITH CIRCUMFLEX
/\\$ @
| |$@
\__/$ @
@@
220 LATIN CAPITAL LETTER U WITH DIAERESIS
_) _)$@
| |$@
\__/$ @
@@
221 LATIN CAPITAL LETTER Y WITH ACUTE
_/$ @
\ \ /$@
_|$ @
@@
222 LATIN CAPITAL LETTER THORN
|$ @
-_)$@
_|$ @
@@
223 LATIN SMALL LETTER SHARP S
_ \$@
|< <$@
|__/$@
_|$ @@
224 LATIN SMALL LETTER A WITH GRAVE
\_\$ @
_` |$@
\__,_|$@
@@
225 LATIN SMALL LETTER A WITH ACUTE
_/$ @
_` |$@
\__,_|$@
@@
226 LATIN SMALL LETTER A WITH CIRCUMFLEX
/\\$ @
_` |$@
\__,_|$@
@@
227 LATIN SMALL LETTER A WITH TILDE
/ _/$ @
_` |$@
\__,_|$@
@@
228 LATIN SMALL LETTER A WITH DIAERESIS
_) _)$@
_` |$@
\__,_|$@
@@
229 LATIN SMALL LETTER A WITH RING ABOVE
( )$ @
_` |$@
\__,_|$@
@@
230 LATIN SMALL LETTER AE
@
_` -_)$@
\__,___|$@
@@
231 LATIN SMALL LETTER C WITH CEDILLA
@
_|$@
\__|$@
_)$@@
232 LATIN SMALL LETTER E WITH GRAVE
\_\$ @
-_)$@
\___|$@
@@
233 LATIN SMALL LETTER E WITH ACUTE
_/$ @
-_)$@
\___|$@
@@
234 LATIN SMALL LETTER E WITH CIRCUMFLEX
/\\$ @
-_)$@
\___|$@
@@
235 LATIN SMALL LETTER E WITH DIAERESIS
_) _)$@
-_)$ @
\___|$ @
@@
236 LATIN SMALL LETTER I WITH GRAVE
\_\$@
|$@
_|$@
@@
237 LATIN SMALL LETTER I WITH ACUTE
_/$@
|$@
_|$@
@@
238 LATIN SMALL LETTER I WITH CIRCUMFLEX
/\\$@
|$ @
_|$ @
@@
239 LATIN SMALL LETTER I WITH DIAERESIS
_) _)$@
|$ @
_|$ @
@@
240 LATIN SMALL LETTER ETH
, \'$@
_` |$@
\___/$ @
@@
241 LATIN SMALL LETTER N WITH TILDE
/ _/$ @
' \$ @
_| _|$@
@@
242 LATIN SMALL LETTER O WITH GRAVE
\_\$ @
_ \$@
\___/$@
@@
243 LATIN SMALL LETTER O WITH ACUTE
_/$ @
_ \$@
\___/$@
@@
244 LATIN SMALL LETTER O WITH CIRCUMFLEX
/\\$ @
_ \$@
\___/$@
@@
245 LATIN SMALL LETTER O WITH TILDE
/ _/$@
_ \$@
\___/$@
@@
246 LATIN SMALL LETTER O WITH DIAERESIS
_) _)$@
_ \$@
\___/$@
@@
247 DIVISION SIGN
_)$ @
___|$@
_)$ @
@@
248 LATIN SMALL LETTER O WITH STROKE
@
/\$@
\_/_/$@
@@
249 LATIN SMALL LETTER U WITH GRAVE
\_\$ @
| |$@
\_,_|$@
@@
250 LATIN SMALL LETTER U WITH ACUTE
_/$ @
| |$@
\_,_|$@
@@
251 LATIN SMALL LETTER U WITH CIRCUMFLEX
/\\$ @
| |$@
\_,_|$@
@@
252 LATIN SMALL LETTER U WITH DIAERESIS
_) _)$@
| |$@
\_,_|$@
@@
253 LATIN SMALL LETTER Y WITH ACUTE
_/$ @
| |$@
\_, |$@
___/$ @@
254 LATIN SMALL LETTER THORN
|$ @
'_ \$@
.__/$@
_|$ @@
255 LATIN SMALL LETTER Y WITH DIAERESIS
_) _)$@
| |$@
\_, |$@
___/$ @@

1097
sevenkeys/fonts/smslant.flf Normal file

File diff suppressed because it is too large Load Diff

2227
sevenkeys/fonts/standard.flf Normal file

File diff suppressed because it is too large Load Diff

600
sevenkeys/fonts/term.flf Normal file
View File

@ -0,0 +1,600 @@
flf2a 1 1 2 -1 13 0 0 242
Terminal by Glenn Chappell 4/93
Includes characters 128-255
Enhanced for Latin-2,3,4 by John Cowan <cowan@ccil.org>
Latin character sets supported only if your screen font does
figlet release 2.2 -- November 1996
Permission is hereby given to modify this font, as long as the
modifier's name is placed on a comment line.
Double-checked by Paul Burton <solution@earthlink.net> 12/96. Added the new
parameter supported by FIGlet and FIGWin. Unlike all other FIGfonts, this one
is intended to produce output exactly the same as the input unless a control
file is used. Therefore it produces the SAME output for smush, kern or fit.
@
!@
"@
#@
$@
%@
&@
'@
(@
)@
*@
+@
,@
-@
.@
/@
0@
1@
2@
3@
4@
5@
6@
7@
8@
9@
:@
;@
<@
=@
>@
?@
@#
A@
B@
C@
D@
E@
F@
G@
H@
I@
J@
K@
L@
M@
N@
O@
P@
Q@
R@
S@
T@
U@
V@
W@
X@
Y@
Z@
[@
\@
]@
^@
_@
`@
a@
b@
c@
d@
e@
f@
g@
h@
i@
j@
k@
l@
m@
n@
o@
p@
q@
r@
s@
t@
u@
v@
w@
x@
y@
z@
{@
|@
}@
~@
128
<EFBFBD>@
129
<EFBFBD>
130
<EFBFBD>
131
<EFBFBD>
132
<EFBFBD>
133
<EFBFBD>
134
<EFBFBD>
135
136
137
𪎩
138
𧶄
139
𣏴
140
141
𠮟
142
𣻗
143
144
145
𥜆
146
𡏆
147
148
149
𦅜
150
151
152
𦴦
153
154
155
𡨭
156
157
𨆉
158
𠺢
159
160 NO-BREAK SPACE
𨩚
161 INVERTED EXCLAMATION MARK
 
162 CENT SIGN
163 POUND SIGN
164 CURRENCY SIGN
165 YEN SIGN
166 BROKEN BAR
167 SECTION SIGN
168 DIAERESIS
169 COPYRIGHT SIGN
170 FEMININE ORDINAL INDICATOR
171 LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
172 NOT SIGN
173 SOFT HYPHEN
174 REGISTERED SIGN
175 MACRON
176 DEGREE SIGN
177 PLUS-MINUS SIGN
178 SUPERSCRIPT TWO
179 SUPERSCRIPT THREE
180 ACUTE ACCENT
181 MICRO SIGN
182 PILCROW SIGN
183 MIDDLE DOT
184 CEDILLA
185 SUPERSCRIPT ONE
186 MASCULINE ORDINAL INDICATOR
187 RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
188 VULGAR FRACTION ONE QUARTER
189 VULGAR FRACTION ONE HALF
190 VULGAR FRACTION THREE QUARTERS
191 INVERTED QUESTION MARK
192 LATIN CAPITAL LETTER A WITH GRAVE
193 LATIN CAPITAL LETTER A WITH ACUTE
194 LATIN CAPITAL LETTER A WITH CIRCUMFLEX
駿
195 LATIN CAPITAL LETTER A WITH TILDE
196 LATIN CAPITAL LETTER A WITH DIAERESIS
197 LATIN CAPITAL LETTER A WITH RING ABOVE
198 LATIN CAPITAL LETTER AE
199 LATIN CAPITAL LETTER C WITH CEDILLA
200 LATIN CAPITAL LETTER E WITH GRAVE
Л
201 LATIN CAPITAL LETTER E WITH ACUTE
202 LATIN CAPITAL LETTER E WITH CIRCUMFLEX
203 LATIN CAPITAL LETTER E WITH DIAERESIS
204 LATIN CAPITAL LETTER I WITH GRAVE
205 LATIN CAPITAL LETTER I WITH ACUTE
206 LATIN CAPITAL LETTER I WITH CIRCUMFLEX
207 LATIN CAPITAL LETTER I WITH DIAERESIS
208 LATIN CAPITAL LETTER ETH
209 LATIN CAPITAL LETTER N WITH TILDE
210 LATIN CAPITAL LETTER O WITH GRAVE
211 LATIN CAPITAL LETTER O WITH ACUTE
212 LATIN CAPITAL LETTER O WITH CIRCUMFLEX
213 LATIN CAPITAL LETTER O WITH TILDE
214 LATIN CAPITAL LETTER O WITH DIAERESIS
215 MULTIPLICATION SIGN
216 LATIN CAPITAL LETTER O WITH STROKE
217 LATIN CAPITAL LETTER U WITH GRAVE
218 LATIN CAPITAL LETTER U WITH ACUTE
219 LATIN CAPITAL LETTER U WITH CIRCUMFLEX
220 LATIN CAPITAL LETTER U WITH DIAERESIS
221 LATIN CAPITAL LETTER Y WITH ACUTE
222 LATIN CAPITAL LETTER THORN
223 LATIN SMALL LETTER SHARP S
224 LATIN SMALL LETTER A WITH GRAVE
225 LATIN SMALL LETTER A WITH ACUTE
226 LATIN SMALL LETTER A WITH CIRCUMFLEX
227 LATIN SMALL LETTER A WITH TILDE
228 LATIN SMALL LETTER A WITH DIAERESIS
229 LATIN SMALL LETTER A WITH RING ABOVE
230 LATIN SMALL LETTER AE
231 LATIN SMALL LETTER C WITH CEDILLA
232 LATIN SMALL LETTER E WITH GRAVE
233 LATIN SMALL LETTER E WITH ACUTE
234 LATIN SMALL LETTER E WITH CIRCUMFLEX
235 LATIN SMALL LETTER E WITH DIAERESIS
236 LATIN SMALL LETTER I WITH GRAVE
237 LATIN SMALL LETTER I WITH ACUTE
238 LATIN SMALL LETTER I WITH CIRCUMFLEX
239 LATIN SMALL LETTER I WITH DIAERESIS
240 LATIN SMALL LETTER ETH
241 LATIN SMALL LETTER N WITH TILDE
242 LATIN SMALL LETTER O WITH GRAVE
徿
243 LATIN SMALL LETTER O WITH ACUTE
244 LATIN SMALL LETTER O WITH CIRCUMFLEX
245 LATIN SMALL LETTER O WITH TILDE
246 LATIN SMALL LETTER O WITH DIAERESIS
247 DIVISION SIGN
248 LATIN SMALL LETTER O WITH STROKE
249 LATIN SMALL LETTER U WITH GRAVE
250 LATIN SMALL LETTER U WITH ACUTE
𠕇
251 LATIN SMALL LETTER U WITH CIRCUMFLEX
𨦼
252 LATIN SMALL LETTER U WITH DIAERESIS
253 LATIN SMALL LETTER Y WITH ACUTE
𣑯
254 LATIN SMALL LETTER THORN
255 LATIN SMALL LETTER Y WITH DIAERESIS
<EFBFBD>@
0x0100 LATIN CAPITAL LETTER A WITH MACRON
0x0101 LATIN SMALL LETTER A WITH MACRON
0x0102 LATIN CAPITAL LETTER A WITH BREVE
0x0103 LATIN SMALL LETTER A WITH BREVE
0x0104 LATIN CAPITAL LETTER A WITH OGONEK
 
0x0105 LATIN SMALL LETTER A WITH OGONEK
0x0106 LATIN CAPITAL LETTER C WITH ACUTE
0x0107 LATIN SMALL LETTER C WITH ACUTE
0x0108 LATIN CAPITAL LETTER C WITH CIRCUMFLEX
0x0109 LATIN SMALL LETTER C WITH CIRCUMFLEX
0x010A LATIN CAPITAL LETTER C WITH DOT ABOVE
0x010B LATIN SMALL LETTER C WITH DOT ABOVE
0x010C LATIN CAPITAL LETTER C WITH CARON
Л
0x010D LATIN SMALL LETTER C WITH CARON
0x010E LATIN CAPITAL LETTER D WITH CARON
0x010F LATIN SMALL LETTER D WITH CARON
0x0110 LATIN CAPITAL LETTER D WITH STROKE
0x0111 LATIN SMALL LETTER D WITH STROKE
0x0112 LATIN CAPITAL LETTER E WITH MACRON
0x0113 LATIN SMALL LETTER E WITH MACRON
0x0116 LATIN CAPITAL LETTER E WITH DOT ABOVE
0x0117 LATIN SMALL LETTER E WITH DOT ABOVE
0x0118 LATIN CAPITAL LETTER E WITH OGONEK
0x0119 LATIN SMALL LETTER E WITH OGONEK
0x011A LATIN CAPITAL LETTER E WITH CARON
0x011B LATIN SMALL LETTER E WITH CARON
0x011C LATIN CAPITAL LETTER G WITH CIRCUMFLEX
0x011D LATIN SMALL LETTER G WITH CIRCUMFLEX
0x011E LATIN CAPITAL LETTER G WITH BREVE
0x011F LATIN SMALL LETTER G WITH BREVE
0x0120 LATIN CAPITAL LETTER G WITH DOT ABOVE
0x0121 LATIN SMALL LETTER G WITH DOT ABOVE
0x0122 LATIN CAPITAL LETTER G WITH CEDILLA
0x0123 LATIN SMALL LETTER G WITH CEDILLA
0x0124 LATIN CAPITAL LETTER H WITH CIRCUMFLEX
0x0125 LATIN SMALL LETTER H WITH CIRCUMFLEX
0x0126 LATIN CAPITAL LETTER H WITH STROKE
 
0x0127 LATIN SMALL LETTER H WITH STROKE
0x0128 LATIN CAPITAL LETTER I WITH TILDE
0x0129 LATIN SMALL LETTER I WITH TILDE
0x012A LATIN CAPITAL LETTER I WITH MACRON
0x012B LATIN SMALL LETTER I WITH MACRON
0x012E LATIN CAPITAL LETTER I WITH OGONEK
0x012F LATIN SMALL LETTER I WITH OGONEK
0x0130 LATIN CAPITAL LETTER I WITH DOT ABOVE
0x0131 LATIN SMALL LETTER DOTLESS I
0x0134 LATIN CAPITAL LETTER J WITH CIRCUMFLEX
0x0135 LATIN SMALL LETTER J WITH CIRCUMFLEX
0x0136 LATIN CAPITAL LETTER K WITH CEDILLA
0x0137 LATIN SMALL LETTER K WITH CEDILLA
0x0138 LATIN SMALL LETTER KRA
0x0139 LATIN CAPITAL LETTER L WITH ACUTE
0x013A LATIN SMALL LETTER L WITH ACUTE
0x013B LATIN CAPITAL LETTER L WITH CEDILLA
0x013C LATIN SMALL LETTER L WITH CEDILLA
0x013D LATIN CAPITAL LETTER L WITH CARON
0x013E LATIN SMALL LETTER L WITH CARON
0x0141 LATIN CAPITAL LETTER L WITH STROKE
0x0142 LATIN SMALL LETTER L WITH STROKE
0x0143 LATIN CAPITAL LETTER N WITH ACUTE
0x0144 LATIN SMALL LETTER N WITH ACUTE
0x0145 LATIN CAPITAL LETTER N WITH CEDILLA
0x0146 LATIN SMALL LETTER N WITH CEDILLA
0x0147 LATIN CAPITAL LETTER N WITH CARON
0x0148 LATIN SMALL LETTER N WITH CARON
徿
0x014A LATIN CAPITAL LETTER ENG
0x014B LATIN SMALL LETTER ENG
0x014C LATIN CAPITAL LETTER O WITH MACRON
0x014D LATIN SMALL LETTER O WITH MACRON
徿
0x0150 LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
0x0151 LATIN SMALL LETTER O WITH DOUBLE ACUTE
0x0154 LATIN CAPITAL LETTER R WITH ACUTE
0x0155 LATIN SMALL LETTER R WITH ACUTE
0x0156 LATIN CAPITAL LETTER R WITH CEDILLA
0x0157 LATIN SMALL LETTER R WITH CEDILLA
0x0158 LATIN CAPITAL LETTER R WITH CARON
0x0159 LATIN SMALL LETTER R WITH CARON
0x015A LATIN CAPITAL LETTER S WITH ACUTE
0x015B LATIN SMALL LETTER S WITH ACUTE
0x015C LATIN CAPITAL LETTER S WITH CIRCUMFLEX
0x015D LATIN SMALL LETTER S WITH CIRCUMFLEX
0x015E LATIN CAPITAL LETTER S WITH CEDILLA
0x015F LATIN SMALL LETTER S WITH CEDILLA
0x0160 LATIN CAPITAL LETTER S WITH CARON
0x0161 LATIN SMALL LETTER S WITH CARON
0x0162 LATIN CAPITAL LETTER T WITH CEDILLA
0x0163 LATIN SMALL LETTER T WITH CEDILLA
0x0164 LATIN CAPITAL LETTER T WITH CARON
0x0165 LATIN SMALL LETTER T WITH CARON
0x0166 LATIN CAPITAL LETTER T WITH STROKE
0x0167 LATIN SMALL LETTER T WITH STROKE
0x0168 LATIN CAPITAL LETTER U WITH TILDE
0x0169 LATIN SMALL LETTER U WITH TILDE
𣑯
0x016A LATIN CAPITAL LETTER U WITH MACRON
0x016B LATIN SMALL LETTER U WITH MACRON
0x016C LATIN CAPITAL LETTER U WITH BREVE
0x016D LATIN SMALL LETTER U WITH BREVE
𣑯
0x016E LATIN CAPITAL LETTER U WITH RING ABOVE
0x016F LATIN SMALL LETTER U WITH RING ABOVE
0x0170 LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
0x0171 LATIN SMALL LETTER U WITH DOUBLE ACUTE
𨦼
0x0172 LATIN CAPITAL LETTER U WITH OGONEK
0x0173 LATIN SMALL LETTER U WITH OGONEK
0x0179 LATIN CAPITAL LETTER Z WITH ACUTE
0x017A LATIN SMALL LETTER Z WITH ACUTE
0x017B LATIN CAPITAL LETTER Z WITH DOT ABOVE
0x017C LATIN SMALL LETTER Z WITH DOT ABOVE
0x017D LATIN CAPITAL LETTER Z WITH CARON
0x017E LATIN SMALL LETTER Z WITH CARON
0x02C7 CARON
0x02D8 BREVE
0x02D9 DOT ABOVE
<EFBFBD>@
0x02DB OGONEK
0x02DD DOUBLE ACUTE ACCENT

View File

@ -2,15 +2,34 @@ module sevenkeys
go 1.22.1 go 1.22.1
require github.com/go-sql-driver/mysql v1.8.1 require (
github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v0.25.0
github.com/go-mysql/errors v0.0.0-20180603193453-03314bea68e0
github.com/go-sql-driver/mysql v1.8.1
)
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/go-mysql/errors v0.0.0-20180603193453-03314bea68e0 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/lipgloss v0.9.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 // indirect github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 // indirect
golang.org/x/crypto v0.23.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
golang.org/x/sys v0.20.0 // indirect github.com/lukesampson/figlet v0.0.0-20190211215653-8a3ef4a6ac42 // indirect
golang.org/x/term v0.20.0 // indirect github.com/mattn/go-isatty v0.0.18 // indirect
gotest.tools/v3 v3.5.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.6 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
) )

View File

@ -1,18 +1,65 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-mysql/errors v0.0.0-20180603193453-03314bea68e0 h1:meiLwrW6ukHHehydhoDxVHdQKQe7TFgEpH0A0hHBAWs= github.com/go-mysql/errors v0.0.0-20180603193453-03314bea68e0 h1:meiLwrW6ukHHehydhoDxVHdQKQe7TFgEpH0A0hHBAWs=
github.com/go-mysql/errors v0.0.0-20180603193453-03314bea68e0/go.mod h1:ZH8V0509n2OSZLMYTMHzcy4hqUB+rG8ghK1zsP4i5gE= github.com/go-mysql/errors v0.0.0-20180603193453-03314bea68e0/go.mod h1:ZH8V0509n2OSZLMYTMHzcy4hqUB+rG8ghK1zsP4i5gE=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 h1:fO9A67/izFYFYky7l1pDP5Dr0BTCRkaQJUG6Jm5ehsk= github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 h1:fO9A67/izFYFYky7l1pDP5Dr0BTCRkaQJUG6Jm5ehsk=
github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3/go.mod h1:Ey4uAp+LvIl+s5jRbOHLcZpUDnkjLBROl15fZLwPlTM= github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3/go.mod h1:Ey4uAp+LvIl+s5jRbOHLcZpUDnkjLBROl15fZLwPlTM=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lukesampson/figlet v0.0.0-20190211215653-8a3ef4a6ac42 h1:UtyD+eBVdLYSj5/pjfSR6mtnzMgIiOVcFT024G2l4CY=
github.com/lukesampson/figlet v0.0.0-20190211215653-8a3ef4a6ac42/go.mod h1:/peI0OaxVYh7fzA72CD7rUsyGVdF7sCiFw7GcYqOcCw=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=

11
sevenkeys/logic/locate.go Normal file
View File

@ -0,0 +1,11 @@
package logic
import (
"database/sql"
"sevenkeys/database"
)
func LocateCards(db *sql.DB, cardName string) ([]database.LocateCardResult, error) {
results, err := database.GetLocateCardResultsByCardName(db, cardName)
return results, err
}

View File

@ -1,6 +1,7 @@
package scryfall package scryfall
type Card struct { type Card struct {
Id string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Set string `json:"set"` Set string `json:"set"`
Games []string `json:"games"` Games []string `json:"games"`
@ -8,5 +9,6 @@ type Card struct {
NonFoil bool `json:"nonfoil"` NonFoil bool `json:"nonfoil"`
Promo bool `json:"promo"` Promo bool `json:"promo"`
CollectorNumber string `json:"collector_number"` CollectorNumber string `json:"collector_number"`
ImageUris map[string]string `json:"image_uris"`
Language string `json:"lang"` Language string `json:"lang"`
} }

View File

@ -10,12 +10,53 @@ import (
"strings" "strings"
) )
// The SearchOptions type is a map of `string`s (which can be searched using fzf) type Triadic int
// to `int`s (which represent a primary key in the CardPrintings table)
type SearchOptions map[string]int
func GetAllSearchOptions(db *sql.DB) (SearchOptions, error) { const (
var searchOptions SearchOptions = make(map[string]int) True Triadic = iota
False
Either
)
type SearchCriteria struct {
SetCode string
Foil Triadic
Promo Triadic
Language string
}
// The SearchOptions type is a map of `string` keys (which can be searched using fzf)
// to `string` values (which represent a primary key in the CardPrintings table)
type SearchOptions map[string]string
func filterPrinting(printing database.CardPrinting, searchCriteria SearchCriteria) bool {
if searchCriteria.SetCode != "" && printing.SetCode != searchCriteria.SetCode {
return true
}
if searchCriteria.Foil == False && printing.IsFoil {
return true
}
if searchCriteria.Foil == True && !printing.IsFoil {
return true
}
if searchCriteria.Promo == False && printing.IsPromo {
return true
}
if searchCriteria.Promo == True && !printing.IsPromo {
return true
}
if searchCriteria.Language != "" && printing.Language != searchCriteria.Language {
return true
}
return false
}
func GetAllSearchOptions(db *sql.DB, searchCriteria SearchCriteria) (SearchOptions, error) {
var searchOptions SearchOptions = make(map[string]string)
cardPrintings, err := database.GetAllCardPrintings(db) cardPrintings, err := database.GetAllCardPrintings(db)
if err != nil { if err != nil {
@ -23,6 +64,13 @@ func GetAllSearchOptions(db *sql.DB) (SearchOptions, error) {
} }
for _, printing := range cardPrintings { for _, printing := range cardPrintings {
// Filter based on search criteria
filter := filterPrinting(printing, searchCriteria)
if filter {
continue
}
// Construct search option string
searchString := fmt.Sprintf("%s (%s %s) [%s]", printing.Name, printing.SetCode, printing.CollectorNumber, printing.Language) searchString := fmt.Sprintf("%s (%s %s) [%s]", printing.Name, printing.SetCode, printing.CollectorNumber, printing.Language)
if printing.IsFoil { if printing.IsFoil {
@ -39,13 +87,13 @@ func GetAllSearchOptions(db *sql.DB) (SearchOptions, error) {
return searchOptions, err return searchOptions, err
} }
func Search(searchOptions SearchOptions) (int, string, error) { func Search(searchOptions SearchOptions) (string, string, error) {
cmd := exec.Command("fzf") cmd := exec.Command("fzf")
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
fzfStdin, err := cmd.StdinPipe() fzfStdin, err := cmd.StdinPipe()
if err != nil { if err != nil {
return -1, "", err return "", "", err
} }
go func() { go func() {
@ -57,7 +105,7 @@ func Search(searchOptions SearchOptions) (int, string, error) {
fzfOutput, err := cmd.Output() fzfOutput, err := cmd.Output()
if err != nil { if err != nil {
return -1, "", err return "", "", err
} }
key := strings.TrimSuffix(string(fzfOutput), "\n") key := strings.TrimSuffix(string(fzfOutput), "\n")

View File

@ -0,0 +1,402 @@
package logic
import (
"sevenkeys/database"
"testing"
)
func TestFilterPrinting_ReturnsTrue_IfSetCodeDoesNotMatch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "otj",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}
func TestFilterPrinting_ReturnsFalse_IfSetCodeDoesMatch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfSetCodeNotSet(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsTrue_IfFoilCardInNonFoilSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: true,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}
func TestFilterPrinting_ReturnsTrue_IfNonFoilCardInFoilSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: True,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}
func TestFilterPrinting_ReturnsFalse_IfNonFoilCardInNonFoilSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfFoilCardInFoilSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: true,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: True,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfFoilCardInEitherFoilSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: true,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: Either,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfNonFoilCardInEitherFoilSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: Either,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsTrue_IfPromoCardInNonPromoSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: true,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}
func TestFilterPrinting_ReturnsTrue_IfNonPromoCardInPromoSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: True,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}
func TestFilterPrinting_ReturnsFalse_IfNonPromoCardInNonPromoSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfPromoCardInPromoSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: true,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: True,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfPromoCardInEitherPromoSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: true,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: Either,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfNonPromoCardInEitherPromoSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: Either,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsTrue_IfLanguageDoesNotMatch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "de",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}
func TestFilterPrinting_ReturnsFalse_IfLanguageDoesMatch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "de",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "de",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfLanguageNotSet(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}

View File

@ -2,18 +2,72 @@ package logic
import ( import (
"database/sql" "database/sql"
"io"
"os"
"os/exec"
"sevenkeys/database" "sevenkeys/database"
"strings"
) )
func StoreCard(db *sql.DB, storageLocation database.CardStorageLocation) error { func CreateStorageArea(db *sql.DB, storageArea database.StorageArea) error {
lastPosition, err := database.GetLastPositionInBox(db, storageLocation.StorageBox) // TODO: Check if there's already a storage are with the same name
if err != nil { // TODO: Check if the type entered is valid
return err err := database.InsertStorageArea(db, storageArea)
} if err != nil {
return err
storageLocation.Position = lastPosition + 1 }
err = database.InsertCardStorageLocation(db, storageLocation) return nil
}
func SelectStorageArea(db *sql.DB) (database.StorageArea, error) {
var selectedStorageArea database.StorageArea
storageAreas, err := database.GetAllStorageAreas(db)
if err != nil {
return selectedStorageArea, err
}
cmd := exec.Command("fzf")
cmd.Stderr = os.Stderr
fzfStdin, err := cmd.StdinPipe()
if err != nil {
return selectedStorageArea, err
}
go func() {
defer fzfStdin.Close()
for _, area := range storageAreas {
io.WriteString(fzfStdin, area.Name+"\n")
}
}()
fzfOutput, err := cmd.Output()
if err != nil {
return selectedStorageArea, err
}
key := strings.TrimSuffix(string(fzfOutput), "\n")
for _, area := range storageAreas {
if area.Name == key {
selectedStorageArea = area
break
}
}
return selectedStorageArea, nil
}
func StoreCard(db *sql.DB, cardLocation database.CardLocation) error {
lastPosition, err := database.GetLastPositionInStorageArea(db, cardLocation.StorageAreaId)
if err != nil {
return err
}
cardLocation.Position = lastPosition + 1
err = database.InsertCardLocation(db, cardLocation)
if err != nil { if err != nil {
return err return err
} }

View File

@ -11,7 +11,6 @@ import (
"os" "os"
"sevenkeys/database" "sevenkeys/database"
"sevenkeys/logic/scryfall" "sevenkeys/logic/scryfall"
"strings"
"time" "time"
sqlerr "github.com/go-mysql/errors" sqlerr "github.com/go-mysql/errors"
@ -35,14 +34,6 @@ func CheckForUpdates(db *sql.DB, bulkData scryfall.BulkData) (bool, error) {
return bulkData.UpdatedAtTime.After(cachedFileTimestamp), nil return bulkData.UpdatedAtTime.After(cachedFileTimestamp), nil
} }
func ConfirmUpdate() bool {
fmt.Print("Run update? (y/N) ")
var response string
fmt.Scan(&response)
return strings.ToUpper(response) == "Y"
}
func UpdateSets(db *sql.DB) error { func UpdateSets(db *sql.DB) error {
sets, err := scryfall.GetAllSets() sets, err := scryfall.GetAllSets()
if err != nil { if err != nil {
@ -127,17 +118,20 @@ func getCardPrintings(card scryfall.Card) []database.CardPrinting {
if card.Foil { if card.Foil {
printings = append(printings, database.CardPrinting{ printings = append(printings, database.CardPrinting{
Id: card.Id + "f",
Name: card.Name, Name: card.Name,
SetCode: card.Set, SetCode: card.Set,
IsFoil: true, IsFoil: true,
IsPromo: card.Promo, IsPromo: card.Promo,
CollectorNumber: card.CollectorNumber, CollectorNumber: card.CollectorNumber,
ImageUrl: card.ImageUris["png"],
Language: card.Language, Language: card.Language,
}) })
} }
if card.NonFoil { if card.NonFoil {
printings = append(printings, database.CardPrinting{ printings = append(printings, database.CardPrinting{
Id: card.Id + "n",
Name: card.Name, Name: card.Name,
SetCode: card.Set, SetCode: card.Set,
IsFoil: false, IsFoil: false,

View File

@ -1,76 +1,18 @@
package main package main
import ( import (
"fmt" "sevenkeys/cli"
"sevenkeys/database" "sevenkeys/database"
"sevenkeys/logic" "sevenkeys/figlet"
"sevenkeys/logic/scryfall"
"github.com/inancgumus/screen"
) )
func main() { func main() {
db := database.GetDatabaseFromConfig("config.json") db := database.GetDatabaseFromConfig("config.json")
figlet.ReadFigletFonts()
fmt.Println("Checking for updates...") cli.ShowSplashScreen()
bulkData, err := scryfall.GetBulkDataByType(scryfall.BulkDataTypeAllCards)
logic.Check(err)
needsUpdate, err := logic.CheckForUpdates(db, bulkData) cli.RunUpdateCheck(db)
logic.Check(err)
if needsUpdate { cli.MainCliLoop(db)
fmt.Println("Update required.")
if logic.ConfirmUpdate() {
fmt.Println("Running update...")
logic.CreateCacheDirectories()
err = logic.UpdateSets(db)
logic.Check(err)
err = logic.UpdateCards(db, bulkData)
logic.Check(err)
}
fmt.Println("Update finished.")
} else {
fmt.Println("No update required.")
}
storageBox := logic.GetResponse("Enter storage box label:")
source := logic.GetResponse("Enter source:")
searchOptions, err := logic.GetAllSearchOptions(db)
logic.Check(err)
for {
screen.Clear()
screen.MoveTopLeft()
fmt.Println("Storage location:", storageBox)
fmt.Println("Source:", source)
selectedCardPrintingId, selectedSearchOption, err := logic.Search(searchOptions)
logic.Check(err)
fmt.Println("Inserted card:", selectedSearchOption)
storageLocation := database.CardStorageLocation{
CardPrintingId: selectedCardPrintingId,
StorageBox: storageBox,
Source: source,
}
err = logic.StoreCard(db, storageLocation)
logic.Check(err)
nextAction := logic.GetResponse("[s]earch again/[r]epeat last insert/[q]uit:")
switch nextAction {
case "s":
continue
case "r":
err = logic.StoreCard(db, storageLocation)
logic.Check(err)
case "q":
break
}
}
} }

188
sevenkeys/tui/home.go Normal file
View File

@ -0,0 +1,188 @@
package tui
import (
"sevenkeys/constants"
"sevenkeys/figlet"
"sevenkeys/tui/searchui"
"sevenkeys/tui/updateui"
"strings"
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
)
type sessionState int
const (
homeView sessionState = iota
updateView
searchView
)
type homeKeyMappings struct {
Update key.Binding
SearchCriteria key.Binding
Search key.Binding
StorageOptions key.Binding
Find key.Binding
Quit key.Binding
}
func (k homeKeyMappings) ShortHelp() []key.Binding {
return []key.Binding{k.Update, k.SearchCriteria, k.Search, k.StorageOptions, k.Find, k.Quit}
}
func (k homeKeyMappings) FullHelp() [][]key.Binding {
return [][]key.Binding{
{k.Update, k.SearchCriteria, k.Search},
{k.StorageOptions, k.Find, k.Quit},
}
}
type MainModel struct {
state sessionState
updateModel tea.Model
searchModel tea.Model
help help.Model
keyMappings homeKeyMappings
}
func NewMainModel() MainModel {
help := help.New()
help.ShortSeparator = help.FullSeparator
updateModel := updateui.NewUpdateModel()
searchModel := searchui.NewSearchModel()
keyMappings := homeKeyMappings{
Update: key.NewBinding(
key.WithKeys("u"),
key.WithHelp("u", "update database"),
),
SearchCriteria: key.NewBinding(
key.WithKeys("c"),
key.WithHelp("c", "card printing search criteria"),
),
Search: key.NewBinding(
key.WithKeys("s"),
key.WithHelp("s", "card printing search"),
),
StorageOptions: key.NewBinding(
key.WithKeys("o"),
key.WithHelp("o", "card storage options"),
),
Find: key.NewBinding(
key.WithKeys("f"),
key.WithHelp("f", "find in storage"),
),
Quit: key.NewBinding(
key.WithKeys("ctrl+c", "q"),
key.WithHelp("q", "quit program"),
),
}
return MainModel{
state: homeView,
updateModel: updateModel,
searchModel: searchModel,
help: help,
keyMappings: keyMappings,
}
}
func (m MainModel) Init() tea.Cmd {
return nil
}
func renderHomeScreen(m MainModel) string {
var ui string
// Display splash screen
ui += figlet.SprintMsgSlant("SEVENKEYS", "center")
ui += figlet.SprintMsgTerm("the ultimate Magic: the Gathering trading card storage system", "center")
// Display help
ui += strings.Repeat("\n", constants.WindowHeight-8) // TODO: Avoid hardcoding height somehow
ui += m.help.View(m.keyMappings)
return ui
}
func (m MainModel) View() string {
var ui string
if constants.WindowWidth <= 0 {
return ui
}
switch m.state {
case homeView:
ui += renderHomeScreen(m)
break
case updateView:
ui += m.updateModel.View()
break
case searchView:
ui += m.searchModel.View()
break
}
return ui
}
func (m MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
constants.WindowHeight = msg.Height
constants.WindowWidth = msg.Width
m.help.Width = msg.Width
case tea.KeyMsg:
switch {
case key.Matches(msg, m.keyMappings.Quit):
return m, tea.Quit
}
case updateui.BackMsg:
m.state = homeView
}
switch m.state {
case homeView:
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.keyMappings.Update):
m.state = updateView
case key.Matches(msg, m.keyMappings.Search):
m.state = searchView
}
}
case updateView:
newUpdate, newCmd := m.updateModel.Update(msg)
newUpdateModel, ok := newUpdate.(updateui.Model)
if !ok {
panic("Could not perform assertion on updateui.Model")
}
m.updateModel = newUpdateModel
cmd = newCmd
case searchView:
newSearch, newCmd := m.searchModel.Update(msg)
newSearchModel, ok := newSearch.(searchui.Model)
if !ok {
panic("Could not perform assertion on searchui.Model")
}
m.searchModel = newSearchModel
cmd = newCmd
}
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}

View File

@ -0,0 +1,44 @@
package searchui
import (
"sevenkeys/figlet"
"sevenkeys/logic"
tea "github.com/charmbracelet/bubbletea"
)
type Model struct {
searchOptions logic.SearchOptions
selectedCardPrintingId int
selectedCardPrintingSearch string
}
func NewSearchModel() Model {
return Model{}
}
func (m Model) Init() tea.Cmd {
return nil
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
/*
var cmd tea.Cmd
switch msg := msg.(type) {
case cardPrintingsLoadedMsg:
m.cardPrintingsLoaded = true
break
}
*/
return m, tea.Quit
}
func (m Model) View() string {
var ui string
ui += figlet.SprintMsgSlant("Insert Cards")
return ui
}

View File

@ -0,0 +1,135 @@
package updateui
import (
"database/sql"
"log"
"sevenkeys/constants"
"sevenkeys/logic"
"sevenkeys/logic/scryfall"
"strings"
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/lukesampson/figlet/figletlib"
)
type BackMsg int
type updateRequiredMsg bool
type errorMsg error
type updateKeyMappings struct {
back key.Binding
quit key.Binding
}
func (k updateKeyMappings) ShortHelp() []key.Binding {
return []key.Binding{k.back, k.quit}
}
func (k updateKeyMappings) FullHelp() [][]key.Binding {
return [][]key.Binding{{k.back, k.quit}}
}
type Model struct {
updateRequired bool
updateFinished bool
canLeave bool
help help.Model
keyMappings updateKeyMappings
}
func NewUpdateModel() Model {
help := help.New()
help.ShortSeparator = help.FullSeparator
keyMappings := updateKeyMappings{
back: key.NewBinding(
key.WithKeys("esc"),
key.WithHelp("esc", "back"),
),
quit: key.NewBinding(
key.WithKeys("q"),
key.WithHelp("q", "quit"),
),
}
return Model{
canLeave: true,
help: help,
keyMappings: keyMappings,
}
}
func (m Model) Init() tea.Cmd {
return nil
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
if m.canLeave {
switch {
case key.Matches(msg, m.keyMappings.back):
return m, backCmd()
}
}
break
case errorMsg:
log.Fatal(msg.Error())
break
case updateRequiredMsg:
m.updateRequired = bool(msg)
break
}
return m, cmd
}
func (m Model) View() string {
var ui string
ui += figletlib.SprintMsg("Update", constants.FigletFontSlant, constants.WindowWidth, constants.FigletFontSlant.Settings(), "left")
ui += strings.Repeat("-", constants.WindowWidth)
ui += "\n"
if m.updateRequired {
ui += "Database is out of date. Running update."
} else {
ui += "No update required."
}
if m.canLeave {
ui += strings.Repeat("\n", constants.WindowHeight-8) // TODO: Hardcoded height (again)
ui += m.help.View(m.keyMappings)
}
return ui
}
func checkUpdateRequiredCmd(db *sql.DB) tea.Cmd {
return func() tea.Msg {
bulkData, err := scryfall.GetBulkDataByType(scryfall.BulkDataTypeAllCards)
if err != nil {
return errorMsg(err)
}
needsUpdate, err := logic.CheckForUpdates(db, bulkData)
if err != nil {
return errorMsg(err)
}
return updateRequiredMsg(needsUpdate)
}
}
func backCmd() tea.Cmd {
return func() tea.Msg {
return BackMsg(1)
}
}