anki_record 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|