anki_record 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rdoc_options +27 -0
- data/.rspec +3 -0
- data/.rubocop.yml +29 -0
- data/CHANGELOG.md +9 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +53 -0
- data/Rakefile +12 -0
- data/anki_record.gemspec +41 -0
- data/lib/anki_record/anki_package.rb +183 -0
- data/lib/anki_record/card_template.rb +91 -0
- data/lib/anki_record/collection.rb +65 -0
- data/lib/anki_record/db/anki_schema_definition.rb +77 -0
- data/lib/anki_record/db/clean_collection21_record.rb +10 -0
- data/lib/anki_record/db/clean_collection2_record.rb +10 -0
- data/lib/anki_record/deck.rb +88 -0
- data/lib/anki_record/deck_options_group.rb +94 -0
- data/lib/anki_record/helpers/shared_constants_helper.rb +11 -0
- data/lib/anki_record/helpers/time_helper.rb +23 -0
- data/lib/anki_record/note_field.rb +63 -0
- data/lib/anki_record/note_type.rb +147 -0
- data/lib/anki_record/version.rb +5 -0
- data/lib/anki_record.rb +25 -0
- metadata +101 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pry"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
require_relative "deck"
|
7
|
+
require_relative "deck_options_group"
|
8
|
+
require_relative "helpers/time_helper"
|
9
|
+
require_relative "note_type"
|
10
|
+
|
11
|
+
module AnkiRecord
|
12
|
+
##
|
13
|
+
# Collection represents the single record in the Anki database `col` table
|
14
|
+
class Collection
|
15
|
+
include AnkiRecord::TimeHelper
|
16
|
+
|
17
|
+
##
|
18
|
+
# An array of the collection's note type objects
|
19
|
+
attr_reader :note_types
|
20
|
+
|
21
|
+
##
|
22
|
+
# An array of the collection's deck objects
|
23
|
+
attr_reader :decks
|
24
|
+
|
25
|
+
##
|
26
|
+
# Instantiates the collection object for the +anki_package+
|
27
|
+
def initialize(anki_package:)
|
28
|
+
setup_collection_instance_variables(anki_package: anki_package)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# rubocop:disable Metrics/MethodLength
|
34
|
+
# rubocop:disable Metrics/AbcSize
|
35
|
+
def setup_collection_instance_variables(anki_package:)
|
36
|
+
@anki_package = anki_package
|
37
|
+
@id = col_record["id"]
|
38
|
+
@crt = col_record["crt"]
|
39
|
+
@last_modified_time = (mod = col_record["mod"]).zero? ? milliseconds_since_epoch : mod
|
40
|
+
@scm = col_record["scm"]
|
41
|
+
@ver = col_record["ver"]
|
42
|
+
@dty = col_record["dty"]
|
43
|
+
@usn = col_record["usn"]
|
44
|
+
@ls = col_record["ls"]
|
45
|
+
@configuration = JSON.parse(col_record["conf"])
|
46
|
+
@note_types = JSON.parse(col_record["models"]).values.map do |model_hash|
|
47
|
+
NoteType.new(collection: self, args: model_hash)
|
48
|
+
end
|
49
|
+
@decks = JSON.parse(col_record["decks"]).values.map do |deck_hash|
|
50
|
+
Deck.new(collection: self, args: deck_hash)
|
51
|
+
end
|
52
|
+
@deck_option_groups = JSON.parse(col_record["dconf"]).values.map do |dconf_hash|
|
53
|
+
DeckOptionsGroup.new(collection: self, args: dconf_hash)
|
54
|
+
end
|
55
|
+
@tags = JSON.parse(col_record["tags"])
|
56
|
+
remove_instance_variable(:@col_record)
|
57
|
+
end
|
58
|
+
# rubocop:enable Metrics/AbcSize
|
59
|
+
# rubocop:enable Metrics/MethodLength
|
60
|
+
|
61
|
+
def col_record
|
62
|
+
@col_record ||= @anki_package.execute("select * from col;").first
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnkiRecord
|
4
|
+
ANKI_SCHEMA_DEFINITION = <<~SQL # :nodoc:
|
5
|
+
CREATE TABLE col (
|
6
|
+
id integer PRIMARY KEY,
|
7
|
+
crt integer NOT NULL,
|
8
|
+
mod integer NOT NULL,
|
9
|
+
scm integer NOT NULL,
|
10
|
+
ver integer NOT NULL,
|
11
|
+
dty integer NOT NULL,
|
12
|
+
usn integer NOT NULL,
|
13
|
+
ls integer NOT NULL,
|
14
|
+
conf text NOT NULL,
|
15
|
+
models text NOT NULL,
|
16
|
+
decks text NOT NULL,
|
17
|
+
dconf text NOT NULL,
|
18
|
+
tags text NOT NULL
|
19
|
+
);
|
20
|
+
CREATE TABLE notes (
|
21
|
+
id integer PRIMARY KEY,
|
22
|
+
guid text NOT NULL,
|
23
|
+
mid integer NOT NULL,
|
24
|
+
mod integer NOT NULL,
|
25
|
+
usn integer NOT NULL,
|
26
|
+
tags text NOT NULL,
|
27
|
+
flds text NOT NULL,
|
28
|
+
sfld integer NOT NULL,
|
29
|
+
csum integer NOT NULL,
|
30
|
+
flags integer NOT NULL,
|
31
|
+
data text NOT NULL
|
32
|
+
);
|
33
|
+
CREATE TABLE cards (
|
34
|
+
id integer PRIMARY KEY,
|
35
|
+
nid integer NOT NULL,
|
36
|
+
did integer NOT NULL,
|
37
|
+
ord integer NOT NULL,
|
38
|
+
mod integer NOT NULL,
|
39
|
+
usn integer NOT NULL,
|
40
|
+
type integer NOT NULL,
|
41
|
+
queue integer NOT NULL,
|
42
|
+
due integer NOT NULL,
|
43
|
+
ivl integer NOT NULL,
|
44
|
+
factor integer NOT NULL,
|
45
|
+
reps integer NOT NULL,
|
46
|
+
lapses integer NOT NULL,
|
47
|
+
left integer NOT NULL,
|
48
|
+
odue integer NOT NULL,
|
49
|
+
odid integer NOT NULL,
|
50
|
+
flags integer NOT NULL,
|
51
|
+
data text NOT NULL
|
52
|
+
);
|
53
|
+
CREATE TABLE revlog (
|
54
|
+
id integer PRIMARY KEY,
|
55
|
+
cid integer NOT NULL,
|
56
|
+
usn integer NOT NULL,
|
57
|
+
ease integer NOT NULL,
|
58
|
+
ivl integer NOT NULL,
|
59
|
+
lastIvl integer NOT NULL,
|
60
|
+
factor integer NOT NULL,
|
61
|
+
time integer NOT NULL,
|
62
|
+
type integer NOT NULL
|
63
|
+
);
|
64
|
+
CREATE INDEX ix_notes_usn ON notes (usn);
|
65
|
+
CREATE INDEX ix_cards_usn ON cards (usn);
|
66
|
+
CREATE INDEX ix_revlog_usn ON revlog (usn);
|
67
|
+
CREATE INDEX ix_cards_nid ON cards (nid);
|
68
|
+
CREATE INDEX ix_cards_sched ON cards (did, queue, due);
|
69
|
+
CREATE INDEX ix_revlog_cid ON revlog (cid);
|
70
|
+
CREATE INDEX ix_notes_csum ON notes (csum);
|
71
|
+
CREATE TABLE graves (
|
72
|
+
usn integer NOT NULL,
|
73
|
+
oid integer NOT NULL,
|
74
|
+
type integer NOT NULL
|
75
|
+
);
|
76
|
+
SQL
|
77
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnkiRecord
|
4
|
+
##
|
5
|
+
# This is the SQL insert statement (as a Ruby string/here document) for the
|
6
|
+
# default col record in the collection.anki21 database exported from a new Anki 2.1.54 profile
|
7
|
+
CLEAN_COLLECTION_21_RECORD = <<~SQL
|
8
|
+
INSERT INTO col VALUES(1,1676883600,0,1676902364657,11,0,0,0,'{"activeDecks":[1],"curDeck":1,"nextPos":1,"schedVer":2,"sortType":"noteFld","estTimes":true,"collapseTime":1200,"dayLearnFirst":false,"curModel":1676902364661,"sortBackwards":false,"newSpread":0,"creationOffset":300,"timeLim":0,"addToCur":true,"dueCounts":true}','{"1676902364664":{"id":1676902364664,"name":"Basic (type in the answer)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}\\n\\n{{type:Back}}","afmt":"{{Front}}\\n\\n<hr id=answer>\\n\\n{{type:Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0,1]]]},"1676902364663":{"id":1676902364663,"name":"Basic (optional reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{#Add Reverse}}{{Back}}{{/Add Reverse}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Add Reverse","ord":2,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"all",[1,2]]]},"1676902364665":{"id":1676902364665,"name":"Cloze","type":1,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Cloze","ord":0,"qfmt":"{{cloze:Text}}","afmt":"{{cloze:Text}}<br>\\n{{Back Extra}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Text","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back Extra","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n.cloze {\\n font-weight: bold;\\n color: blue;\\n}\\n.nightMode .cloze {\\n color: lightblue;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]},"1676902364661":{"id":1676902364661,"name":"Basic","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]},"1676902364662":{"id":1676902364662,"name":"Basic (and reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{Back}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"any",[1]]]}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"lrnToday":[0,0],"revToday":[0,0],"newToday":[0,0],"timeToday":[0,0],"collapsed":true,"browserCollapsed":true,"desc":"","dyn":0,"conf":1,"extendNew":0,"extendRev":0}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"maxTaken":60,"autoplay":true,"timer":0,"replayq":true,"new":{"bury":false,"delays":[1.0,10.0],"initialFactor":2500,"ints":[1,4,0],"order":1,"perDay":20},"rev":{"bury":false,"ease4":1.3,"ivlFct":1.0,"maxIvl":36500,"perDay":200,"hardFactor":1.2},"lapse":{"delays":[10.0],"leechAction":1,"leechFails":8,"minInt":1,"mult":0.0},"dyn":false,"newMix":0,"newPerDayMinimum":0,"interdayLearningMix":0,"reviewOrder":0,"newSortOrder":0,"newGatherPriority":0,"buryInterdayLearning":false}}','{}');
|
9
|
+
SQL
|
10
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnkiRecord
|
4
|
+
##
|
5
|
+
# This is the SQL insert statement (as a Ruby string/here document) for the
|
6
|
+
# default col record in the collection.anki2 database exported from a new Anki 2.1.54 profile
|
7
|
+
CLEAN_COLLECTION_2_RECORD = <<~SQL
|
8
|
+
INSERT INTO col VALUES(1,1676883600,1676902390012,1676902390005,11,0,0,0,'{"addToCur":true,"_deck_1_lastNotetype":1676902390008,"nextPos":2,"sortType":"noteFld","newSpread":0,"schedVer":2,"collapseTime":1200,"estTimes":true,"curDeck":1,"creationOffset":300,"dayLearnFirst":false,"timeLim":0,"activeDecks":[1],"sortBackwards":false,"_nt_1676902390008_lastDeck":1,"curModel":1676902390008,"dueCounts":true}','{"1676902390010":{"id":1676902390010,"name":"Basic (optional reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{#Add Reverse}}{{Back}}{{/Add Reverse}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Add Reverse","ord":2,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"all",[1,2]]]},"1676902390009":{"id":1676902390009,"name":"Basic (and reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{Back}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"any",[1]]]},"1676902390008":{"id":1676902390008,"name":"Basic","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]},"1676902390011":{"id":1676902390011,"name":"Basic (type in the answer)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}\\n\\n{{type:Back}}","afmt":"{{Front}}\\n\\n<hr id=answer>\\n\\n{{type:Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0,1]]]},"1676902390012":{"id":1676902390012,"name":"Cloze","type":1,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Cloze","ord":0,"qfmt":"{{cloze:Text}}","afmt":"{{cloze:Text}}<br>\\n{{Back Extra}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Text","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back Extra","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n.cloze {\\n font-weight: bold;\\n color: blue;\\n}\\n.nightMode .cloze {\\n color: lightblue;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"lrnToday":[0,0],"revToday":[0,0],"newToday":[0,0],"timeToday":[0,0],"collapsed":true,"browserCollapsed":true,"desc":"","dyn":0,"conf":1,"extendNew":0,"extendRev":0}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"maxTaken":60,"autoplay":true,"timer":0,"replayq":true,"new":{"bury":false,"delays":[1.0,10.0],"initialFactor":2500,"ints":[1,4,0],"order":1,"perDay":20},"rev":{"bury":false,"ease4":1.3,"ivlFct":1.0,"maxIvl":36500,"perDay":200,"hardFactor":1.2},"lapse":{"delays":[10.0],"leechAction":1,"leechFails":8,"minInt":1,"mult":0.0},"dyn":false,"newMix":0,"newPerDayMinimum":0,"interdayLearningMix":0,"reviewOrder":0,"newSortOrder":0,"newGatherPriority":0,"buryInterdayLearning":false}}','{}');
|
9
|
+
SQL
|
10
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pry"
|
4
|
+
|
5
|
+
require_relative "helpers/shared_constants_helper"
|
6
|
+
require_relative "helpers/time_helper"
|
7
|
+
|
8
|
+
# TODO: All instance variables should at least be readable
|
9
|
+
|
10
|
+
module AnkiRecord
|
11
|
+
##
|
12
|
+
# Deck represents an Anki deck
|
13
|
+
class Deck
|
14
|
+
include SharedConstantsHelper
|
15
|
+
include TimeHelper
|
16
|
+
|
17
|
+
DEFAULT_DECK_TODAY_ARRAY = [0, 0].freeze
|
18
|
+
DEFAULT_COLLAPSED = false
|
19
|
+
|
20
|
+
private_constant :DEFAULT_DECK_TODAY_ARRAY, :DEFAULT_COLLAPSED
|
21
|
+
|
22
|
+
##
|
23
|
+
# The name of the deck
|
24
|
+
attr_accessor :name
|
25
|
+
|
26
|
+
##
|
27
|
+
# The description of the deck
|
28
|
+
attr_accessor :description
|
29
|
+
|
30
|
+
##
|
31
|
+
# One of many attributes that is currently read-only and needs to be documented.
|
32
|
+
attr_reader :collection, :id, :last_modified_time, :deck_options_group_id
|
33
|
+
|
34
|
+
##
|
35
|
+
# Instantiate a new Deck
|
36
|
+
def initialize(collection:, name: nil, args: nil)
|
37
|
+
raise ArgumentError unless (name && args.nil?) || (args && args["name"])
|
38
|
+
|
39
|
+
@collection = collection
|
40
|
+
if args
|
41
|
+
setup_deck_instance_variables_from_existing(args: args)
|
42
|
+
else
|
43
|
+
setup_deck_instance_variables(name: name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# rubocop:disable Metrics/MethodLength
|
50
|
+
# rubocop:disable Metrics/AbcSize
|
51
|
+
def setup_deck_instance_variables_from_existing(args:)
|
52
|
+
@id = args["id"]
|
53
|
+
@last_modified_time = args["mod"]
|
54
|
+
@name = args["name"]
|
55
|
+
@usn = args["usn"]
|
56
|
+
@learn_today = args["lrnToday"]
|
57
|
+
@review_today = args["revToday"]
|
58
|
+
@new_today = args["newToday"]
|
59
|
+
@time_today = args["timeToday"]
|
60
|
+
@collapsed_in_main_window = args["collapsed"]
|
61
|
+
@collapsed_in_browser = args["browserCollapsed"]
|
62
|
+
@description = args["desc"]
|
63
|
+
@dyn = args["dyn"]
|
64
|
+
@deck_options_group_id = args["conf"]
|
65
|
+
@extend_new = args["extendNew"]
|
66
|
+
@extend_review = args["extendRev"]
|
67
|
+
end
|
68
|
+
# rubocop:enable Metrics/MethodLength
|
69
|
+
# rubocop:enable Metrics/AbcSize
|
70
|
+
|
71
|
+
# rubocop:disable Metrics/MethodLength
|
72
|
+
def setup_deck_instance_variables(name:)
|
73
|
+
@id = milliseconds_since_epoch
|
74
|
+
@last_modified_time = seconds_since_epoch
|
75
|
+
@name = name
|
76
|
+
@usn = NEW_OBJECT_USN
|
77
|
+
@learn_today = @review_today = @new_today = @time_today = DEFAULT_DECK_TODAY_ARRAY
|
78
|
+
@collapsed_in_main_window = DEFAULT_COLLAPSED
|
79
|
+
@collapsed_in_browser = DEFAULT_COLLAPSED
|
80
|
+
@description = ""
|
81
|
+
@dyn = NON_FILTERED_DECK_DYN
|
82
|
+
@deck_options_group_id = nil # TODO
|
83
|
+
@extend_new = 0
|
84
|
+
@extend_review = 0
|
85
|
+
end
|
86
|
+
# rubocop:enable Metrics/MethodLength
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pry"
|
4
|
+
|
5
|
+
require_relative "helpers/shared_constants_helper"
|
6
|
+
require_relative "helpers/time_helper"
|
7
|
+
|
8
|
+
module AnkiRecord
|
9
|
+
##
|
10
|
+
# Represents the set of options that can be applied to a deck
|
11
|
+
class DeckOptionsGroup
|
12
|
+
include SharedConstantsHelper
|
13
|
+
include TimeHelper
|
14
|
+
|
15
|
+
##
|
16
|
+
# The name of the options group
|
17
|
+
attr_accessor :name
|
18
|
+
|
19
|
+
##
|
20
|
+
# One of many attributes that is currently read-only and needs to be documented.
|
21
|
+
attr_reader :collection, :id, :last_modified_time, :usn, :max_taken, :auto_play, :timer, :replay_question,
|
22
|
+
:new_options, :review_options, :lapse_options, :dyn, :new_mix, :new_per_day_minimum,
|
23
|
+
:interday_learning_mix, :review_order, :new_sort_order, :new_gather_priority, :bury_interday_learning
|
24
|
+
|
25
|
+
##
|
26
|
+
# Instantiates a new deck options group called +name+ with defaults
|
27
|
+
def initialize(collection:, name: nil, args: nil)
|
28
|
+
# TODO: extract this check to a shared helper
|
29
|
+
raise ArgumentError unless (name && args.nil?) || (args && args["name"])
|
30
|
+
|
31
|
+
@collection = collection
|
32
|
+
|
33
|
+
if args
|
34
|
+
setup_deck_options_group_instance_variables_from_existing(args: args)
|
35
|
+
else
|
36
|
+
setup_deck_options_group_instance_variables(name: name)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# rubocop:disable Metrics/MethodLength
|
43
|
+
# rubocop:disable Metrics/AbcSize
|
44
|
+
def setup_deck_options_group_instance_variables_from_existing(args:)
|
45
|
+
@id = args["id"]
|
46
|
+
@last_modified_time = args["mod"]
|
47
|
+
@name = args["name"]
|
48
|
+
@usn = args["usn"]
|
49
|
+
@max_taken = args["maxTaken"]
|
50
|
+
@auto_play = args["autoplay"]
|
51
|
+
@timer = args["timer"]
|
52
|
+
@replay_question = args["true"]
|
53
|
+
@new_options = args["new"]
|
54
|
+
@review_options = args["rev"]
|
55
|
+
@lapse_options = args["lapse"]
|
56
|
+
@dyn = args["dyn"]
|
57
|
+
@new_mix = args["newMix"]
|
58
|
+
@new_per_day_minimum = args["newPerDayMinimum"]
|
59
|
+
@interday_learning_mix = args["interdayLearningMix"]
|
60
|
+
@review_order = args["reviewOrder"]
|
61
|
+
@new_sort_order = args["new_sort_order"]
|
62
|
+
@new_gather_priority = args["newGatherPriority"]
|
63
|
+
@bury_interday_learning = args["buryInterdayLearning"]
|
64
|
+
end
|
65
|
+
# rubocop:enable Metrics/AbcSize
|
66
|
+
# rubocop:enable Metrics/MethodLength
|
67
|
+
|
68
|
+
# rubocop:disable Metrics/MethodLength
|
69
|
+
# rubocop:disable Metrics/AbcSize
|
70
|
+
def setup_deck_options_group_instance_variables(name:)
|
71
|
+
@id = milliseconds_since_epoch
|
72
|
+
@last_modified_time = seconds_since_epoch
|
73
|
+
@name = name
|
74
|
+
@usn = NEW_OBJECT_USN
|
75
|
+
@max_taken = 60
|
76
|
+
@auto_play = true
|
77
|
+
@timer = 0
|
78
|
+
@replay_question = true
|
79
|
+
@new_options = { bury: false, delays: [1.0, 10.0], initialFactor: 2500, ints: [1, 4, 0], order: 1, perDay: 20 }
|
80
|
+
@review_options = { bury: false, ease4: 1.3, ivlFct: 1.0, maxIvl: 36_500, perDay: 200, hardFactor: 1.2 }
|
81
|
+
@lapse_options = { delays: [10.0], leechAction: 1, leechFails: 8, minInt: 1, mult: 0.0 }
|
82
|
+
@dyn = NON_FILTERED_DECK_DYN
|
83
|
+
@new_mix = 0
|
84
|
+
@new_per_day_minimum = 0
|
85
|
+
@interday_learning_mix = 0
|
86
|
+
@review_order = 0
|
87
|
+
@new_sort_order = 0
|
88
|
+
@new_gather_priority = 0
|
89
|
+
@bury_interday_learning = false
|
90
|
+
end
|
91
|
+
# rubocop:enable Metrics/AbcSize
|
92
|
+
# rubocop:enable Metrics/MethodLength
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnkiRecord
|
4
|
+
##
|
5
|
+
# Helper module to hold the constants used by multiple classes
|
6
|
+
module SharedConstantsHelper
|
7
|
+
NEW_OBJECT_USN = -1
|
8
|
+
NON_FILTERED_DECK_DYN = 0
|
9
|
+
private_constant :NEW_OBJECT_USN, :NON_FILTERED_DECK_DYN
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
|
5
|
+
module AnkiRecord
|
6
|
+
##
|
7
|
+
# Helper module to calculate integer time values since the 1970 epoch
|
8
|
+
#
|
9
|
+
# Specifically, the time that has passed since 00:00:00 UTC Jan 1 1970
|
10
|
+
module TimeHelper
|
11
|
+
##
|
12
|
+
# Return the number of milliseconds since the 1970 epoch
|
13
|
+
def milliseconds_since_epoch
|
14
|
+
DateTime.now.strftime("%Q").to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Return the number of seconds since the 1970 epoch
|
19
|
+
def seconds_since_epoch
|
20
|
+
Time.now.to_i
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TODO: raise exceptions on invalid assignment to prevent downstream errors and specs for the writeable attributes
|
4
|
+
# TODO: All instance variables should at least be readable
|
5
|
+
|
6
|
+
require "pry"
|
7
|
+
|
8
|
+
module AnkiRecord
|
9
|
+
##
|
10
|
+
# NoteField represents a field of an Anki note type
|
11
|
+
class NoteField
|
12
|
+
DEFAULT_FIELD_FONT_STYLE = "Arial"
|
13
|
+
DEFAULT_FIELD_FONT_SIZE = 20
|
14
|
+
DEFAULT_FIELD_DESCRIPTION = ""
|
15
|
+
private_constant :DEFAULT_FIELD_FONT_STYLE, :DEFAULT_FIELD_FONT_SIZE, :DEFAULT_FIELD_DESCRIPTION
|
16
|
+
|
17
|
+
##
|
18
|
+
# The name of the note field
|
19
|
+
attr_accessor :name
|
20
|
+
|
21
|
+
##
|
22
|
+
# One of many attributes that is readable and writeable but needs to be documented
|
23
|
+
attr_accessor :sticky, :right_to_left, :font_style, :font_size, :description
|
24
|
+
|
25
|
+
##
|
26
|
+
# One of many attributes that is currently read-only and needs to be documented.
|
27
|
+
attr_reader :note_type, :ordinal_number
|
28
|
+
|
29
|
+
##
|
30
|
+
# Instantiates a new field for the given note type
|
31
|
+
def initialize(note_type:, name: nil, args: nil)
|
32
|
+
raise ArgumentError unless (name && args.nil?) || (args && args["name"])
|
33
|
+
|
34
|
+
@note_type = note_type
|
35
|
+
if args
|
36
|
+
setup_note_field_instance_variables_from_existing(args: args)
|
37
|
+
else
|
38
|
+
setup_note_field_instance_variables(name: name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def setup_note_field_instance_variables_from_existing(args:)
|
45
|
+
@name = args["name"]
|
46
|
+
@ordinal_number = args["ord"]
|
47
|
+
@sticky = args["sticky"]
|
48
|
+
@right_to_left = args["rtl"]
|
49
|
+
@font_style = args["font"]
|
50
|
+
@font_size = args["size"]
|
51
|
+
end
|
52
|
+
|
53
|
+
def setup_note_field_instance_variables(name:)
|
54
|
+
@name = name
|
55
|
+
@ordinal_number = @note_type.fields.length
|
56
|
+
@sticky = false
|
57
|
+
@right_to_left = false
|
58
|
+
@font_style = DEFAULT_FIELD_FONT_STYLE
|
59
|
+
@font_size = DEFAULT_FIELD_FONT_SIZE
|
60
|
+
@description = DEFAULT_FIELD_DESCRIPTION
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pry"
|
4
|
+
|
5
|
+
require_relative "card_template"
|
6
|
+
require_relative "helpers/shared_constants_helper"
|
7
|
+
require_relative "helpers/time_helper"
|
8
|
+
require_relative "note_field"
|
9
|
+
|
10
|
+
# TODO: All instance variables should at least be readable
|
11
|
+
|
12
|
+
module AnkiRecord
|
13
|
+
##
|
14
|
+
# NoteType represents an Anki note type (also called a model)
|
15
|
+
class NoteType
|
16
|
+
include AnkiRecord::SharedConstantsHelper
|
17
|
+
include AnkiRecord::TimeHelper
|
18
|
+
NEW_NOTE_TYPE_SORT_FIELD = 0
|
19
|
+
private_constant :NEW_NOTE_TYPE_SORT_FIELD
|
20
|
+
|
21
|
+
##
|
22
|
+
# The name of this note type
|
23
|
+
attr_accessor :name
|
24
|
+
|
25
|
+
##
|
26
|
+
# true if the note type is a cloze note type and false if it is not
|
27
|
+
attr_accessor :cloze
|
28
|
+
|
29
|
+
##
|
30
|
+
# One of many attributes that is readable and writeable but needs to be documented
|
31
|
+
attr_accessor :css, :latex_preamble, :latex_postamble, :latex_svg
|
32
|
+
|
33
|
+
##
|
34
|
+
# One of many attributes that is currently read-only and needs to be documented.
|
35
|
+
attr_reader :id, :templates, :fields, :deck_id
|
36
|
+
|
37
|
+
##
|
38
|
+
# Instantiates a new note type
|
39
|
+
def initialize(collection:, name: nil, cloze: false, args: nil)
|
40
|
+
raise ArgumentError unless (name && args.nil?) || (args && args["name"])
|
41
|
+
|
42
|
+
@collection = collection
|
43
|
+
|
44
|
+
if args
|
45
|
+
setup_note_type_instance_variables_from_existing(args: args)
|
46
|
+
else
|
47
|
+
setup_note_type_instance_variables(
|
48
|
+
name: name, cloze: cloze
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Create a new field and adds it to this note type's fields
|
55
|
+
#
|
56
|
+
# The field is an instance of AnkiRecord::NoteField
|
57
|
+
def new_note_field(name:)
|
58
|
+
# TODO: Check if name already used by a field in this note type
|
59
|
+
@fields << AnkiRecord::NoteField.new(note_type: self, name: name)
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Create a new card template and adds it to this note type's templates
|
64
|
+
#
|
65
|
+
# The card template is an instance of AnkiRecord::CardTemplate
|
66
|
+
def new_card_template(name:)
|
67
|
+
# TODO: Check if name already used by a template in this note type
|
68
|
+
@templates << AnkiRecord::CardTemplate.new(note_type: self, name: name)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# rubocop:disable Metrics/MethodLength
|
74
|
+
# rubocop:disable Metrics/AbcSize
|
75
|
+
def setup_note_type_instance_variables_from_existing(args:)
|
76
|
+
@id = args["id"]
|
77
|
+
@name = args["name"]
|
78
|
+
@cloze = args["type"] == 1
|
79
|
+
@last_modified_time = args["mod"]
|
80
|
+
@usn = args["usn"]
|
81
|
+
@sort_field = args["sortf"]
|
82
|
+
@deck_id = args["did"]
|
83
|
+
@fields = args["flds"].map { |fld| NoteField.new(note_type: self, args: fld) }
|
84
|
+
@templates = args["tmpls"].map { |tmpl| CardTemplate.new(note_type: self, args: tmpl) }
|
85
|
+
@css = args["css"]
|
86
|
+
@latex_preamble = args["latexPre"]
|
87
|
+
@latex_postamble = args["latexPost"]
|
88
|
+
@latex_svg = args["latexsvg"]
|
89
|
+
@req = args["req"]
|
90
|
+
@tags = args["tags"]
|
91
|
+
@vers = args["vers"]
|
92
|
+
end
|
93
|
+
# rubocop:enable Metrics/MethodLength
|
94
|
+
# rubocop:enable Metrics/AbcSize
|
95
|
+
|
96
|
+
# rubocop:disable Metrics/MethodLength
|
97
|
+
def setup_note_type_instance_variables(name:, cloze:)
|
98
|
+
@id = milliseconds_since_epoch
|
99
|
+
@name = name
|
100
|
+
@cloze = cloze
|
101
|
+
@last_modified_time = seconds_since_epoch
|
102
|
+
@usn = NEW_OBJECT_USN
|
103
|
+
@sort_field = NEW_NOTE_TYPE_SORT_FIELD
|
104
|
+
@deck_id = nil
|
105
|
+
@fields = []
|
106
|
+
@templates = []
|
107
|
+
@css = default_css
|
108
|
+
@latex_preamble = default_latex_preamble
|
109
|
+
@latex_postamble = default_latex_postamble
|
110
|
+
@latex_svg = false
|
111
|
+
@req = nil
|
112
|
+
@tags = []
|
113
|
+
@vers = []
|
114
|
+
end
|
115
|
+
# rubocop:enable Metrics/MethodLength
|
116
|
+
|
117
|
+
# TODO: use constant here
|
118
|
+
def default_css
|
119
|
+
<<-CSS
|
120
|
+
.card {
|
121
|
+
color: black;
|
122
|
+
background-color: transparent;
|
123
|
+
text-align: center;
|
124
|
+
}
|
125
|
+
CSS
|
126
|
+
end
|
127
|
+
|
128
|
+
# TODO: use constant here
|
129
|
+
def default_latex_preamble
|
130
|
+
<<-LATEX_PRE
|
131
|
+
\\documentclass[12pt]{article}
|
132
|
+
\\special{papersize=3in,5in}
|
133
|
+
\\usepackage{amssymb,amsmath}
|
134
|
+
\\pagestyle{empty}
|
135
|
+
\\setlength{\\parindent}{0in}
|
136
|
+
\\begin{document}
|
137
|
+
LATEX_PRE
|
138
|
+
end
|
139
|
+
|
140
|
+
# TODO: use constant here
|
141
|
+
def default_latex_postamble
|
142
|
+
<<-LATEX_POST
|
143
|
+
\\end{document}
|
144
|
+
LATEX_POST
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
data/lib/anki_record.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
require "sqlite3"
|
5
|
+
require "zip"
|
6
|
+
|
7
|
+
require_relative "anki_record/anki_package"
|
8
|
+
require_relative "anki_record/version"
|
9
|
+
|
10
|
+
##
|
11
|
+
# This module is the namespace for all AnkiRecord classes:
|
12
|
+
# - AnkiPackage
|
13
|
+
# - CardTemplate
|
14
|
+
# - Collection
|
15
|
+
# - DeckOptionsGroup
|
16
|
+
# - Deck
|
17
|
+
# - NoteField
|
18
|
+
# - NoteType
|
19
|
+
#
|
20
|
+
# And modules:
|
21
|
+
# - SharedConstantsHelper
|
22
|
+
# - TimeHelper
|
23
|
+
module AnkiRecord
|
24
|
+
class Error < StandardError; end # :nodoc:
|
25
|
+
end
|