anki_record 0.2.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +13 -2
- data/CHANGELOG.md +37 -9
- data/Gemfile +3 -1
- data/Gemfile.lock +10 -2
- data/README.md +120 -35
- data/anki_record.gemspec +1 -5
- data/lib/anki_record/anki_package/anki_package.rb +237 -0
- data/lib/anki_record/card/card.rb +108 -0
- data/lib/anki_record/card/card_attributes.rb +39 -0
- data/lib/anki_record/card_template/card_template.rb +64 -0
- data/lib/anki_record/card_template/card_template_attributes.rb +69 -0
- data/lib/anki_record/collection/collection.rb +182 -0
- data/lib/anki_record/collection/collection_attributes.rb +35 -0
- data/lib/anki_record/database_setup_constants.rb +88 -0
- data/lib/anki_record/deck/deck.rb +99 -0
- data/lib/anki_record/deck/deck_attributes.rb +30 -0
- data/lib/anki_record/deck/deck_defaults.rb +19 -0
- data/lib/anki_record/{deck_options_group.rb → deck_options_group/deck_options_group.rb} +12 -31
- data/lib/anki_record/deck_options_group/deck_options_group_attributes.rb +23 -0
- data/lib/anki_record/helpers/checksum_helper.rb +10 -11
- data/lib/anki_record/helpers/data_query_helper.rb +15 -0
- data/lib/anki_record/helpers/shared_constants_helper.rb +6 -6
- data/lib/anki_record/helpers/time_helper.rb +18 -13
- data/lib/anki_record/note/note.rb +178 -0
- data/lib/anki_record/note/note_attributes.rb +56 -0
- data/lib/anki_record/note_field/note_field.rb +62 -0
- data/lib/anki_record/note_field/note_field_attributes.rb +39 -0
- data/lib/anki_record/note_field/note_field_defaults.rb +19 -0
- data/lib/anki_record/note_type/note_type.rb +161 -0
- data/lib/anki_record/note_type/note_type_attributes.rb +80 -0
- data/lib/anki_record/note_type/note_type_defaults.rb +38 -0
- data/lib/anki_record/version.rb +1 -1
- data/lib/anki_record.rb +1 -16
- metadata +26 -16
- data/lib/anki_record/anki_package.rb +0 -194
- data/lib/anki_record/card.rb +0 -75
- data/lib/anki_record/card_template.rb +0 -105
- data/lib/anki_record/collection.rb +0 -105
- data/lib/anki_record/db/anki_schema_definition.rb +0 -77
- data/lib/anki_record/db/clean_collection21_record.rb +0 -10
- data/lib/anki_record/db/clean_collection2_record.rb +0 -10
- data/lib/anki_record/deck.rb +0 -101
- data/lib/anki_record/note.rb +0 -135
- data/lib/anki_record/note_field.rb +0 -84
- data/lib/anki_record/note_type.rb +0 -233
data/lib/anki_record/card.rb
DELETED
@@ -1,75 +0,0 @@
|
|
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
|
-
# Card represents an Anki card.
|
11
|
-
class Card
|
12
|
-
include TimeHelper
|
13
|
-
include SharedConstantsHelper
|
14
|
-
|
15
|
-
##
|
16
|
-
# The note that the card belongs to
|
17
|
-
attr_reader :note
|
18
|
-
|
19
|
-
##
|
20
|
-
# The card template that the card uses
|
21
|
-
attr_reader :card_template
|
22
|
-
|
23
|
-
##
|
24
|
-
# The id of the card, which is time it was created as the number of milliseconds since the 1970 epoch
|
25
|
-
attr_reader :id
|
26
|
-
|
27
|
-
##
|
28
|
-
# The time that the card was last modified as the number of seconds since the 1970 epoch
|
29
|
-
attr_reader :last_modified_time
|
30
|
-
|
31
|
-
# rubocop:disable Metrics/MethodLength
|
32
|
-
# rubocop:disable Metrics/AbcSize
|
33
|
-
def initialize(note:, card_template:)
|
34
|
-
raise ArgumentError unless note && card_template && note.note_type == card_template.note_type
|
35
|
-
|
36
|
-
@note = note
|
37
|
-
@apkg = @note.deck.collection.anki_package
|
38
|
-
|
39
|
-
@card_template = card_template
|
40
|
-
|
41
|
-
@id = milliseconds_since_epoch
|
42
|
-
@last_modified_time = seconds_since_epoch
|
43
|
-
@usn = NEW_OBJECT_USN
|
44
|
-
@type = 0
|
45
|
-
@queue = 0
|
46
|
-
@due = 0
|
47
|
-
@ivl = 0
|
48
|
-
@factor = 0
|
49
|
-
@reps = 0
|
50
|
-
@lapses = 0
|
51
|
-
@left = 0
|
52
|
-
@odue = 0
|
53
|
-
@odid = 0
|
54
|
-
@flags = 0
|
55
|
-
@data = {}
|
56
|
-
end
|
57
|
-
# rubocop:enable Metrics/MethodLength
|
58
|
-
# rubocop:enable Metrics/AbcSize
|
59
|
-
|
60
|
-
##
|
61
|
-
# Saves the card to the collection.anki21 database
|
62
|
-
def save
|
63
|
-
@apkg.execute <<~SQL
|
64
|
-
insert into cards (id, nid, did, ord,
|
65
|
-
mod, usn, type, queue,
|
66
|
-
due, ivl, factor, reps,
|
67
|
-
lapses, left, odue, odid, flags, data)
|
68
|
-
values ('#{@id}', '#{@note.id}', '#{@note.deck.id}', '#{@card_template.ordinal_number}',
|
69
|
-
'#{@last_modified_time}', '#{@usn}', '#{@type}', '#{@queue}',
|
70
|
-
'#{@due}', '#{@ivl}', '#{@factor}', '#{@reps}',
|
71
|
-
'#{@lapses}', '#{@left}', '#{@odue}', '#{@odid}', '#{@flags}', '#{@data}')
|
72
|
-
SQL
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
@@ -1,105 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "pry"
|
4
|
-
|
5
|
-
# TODO: All instance variables should at least be readable
|
6
|
-
|
7
|
-
module AnkiRecord
|
8
|
-
##
|
9
|
-
# CardTemplate represents a card template of an Anki note type
|
10
|
-
class CardTemplate
|
11
|
-
##
|
12
|
-
# The name of the card template
|
13
|
-
attr_accessor :name
|
14
|
-
|
15
|
-
##
|
16
|
-
# The font style shown for the card template in the browser
|
17
|
-
attr_accessor :browser_font_style
|
18
|
-
|
19
|
-
##
|
20
|
-
# The font size used for the card template in the browser
|
21
|
-
attr_accessor :browser_font_size
|
22
|
-
|
23
|
-
##
|
24
|
-
# The question format of the card template
|
25
|
-
attr_reader :question_format
|
26
|
-
|
27
|
-
##
|
28
|
-
# Sets the question format and raises an ArgumentError if the specified format uses invalid fields
|
29
|
-
def question_format=(format)
|
30
|
-
fields_in_specified_format = format.scan(/{{.+?}}/).map do |capture|
|
31
|
-
capture.chomp("}}").reverse.chomp("{{").reverse
|
32
|
-
end
|
33
|
-
raise ArgumentError if fields_in_specified_format.any? do |field_name|
|
34
|
-
!note_type.allowed_card_template_question_format_field_names.include?(field_name)
|
35
|
-
end
|
36
|
-
|
37
|
-
@question_format = format
|
38
|
-
end
|
39
|
-
|
40
|
-
##
|
41
|
-
# The answer format of the card template
|
42
|
-
attr_reader :answer_format
|
43
|
-
|
44
|
-
##
|
45
|
-
# Sets the answer format and raises an ArgumentError if the specified format uses invalid fields
|
46
|
-
def answer_format=(format)
|
47
|
-
fields_in_specified_format = format.scan(/{{.+?}}/).map do |capture|
|
48
|
-
capture.chomp("}}").reverse.chomp("{{").reverse
|
49
|
-
end
|
50
|
-
raise ArgumentError if fields_in_specified_format.any? do |field_name|
|
51
|
-
!note_type.allowed_card_template_answer_format_field_names.include?(field_name)
|
52
|
-
end
|
53
|
-
|
54
|
-
@answer_format = format
|
55
|
-
end
|
56
|
-
|
57
|
-
##
|
58
|
-
# The note type that the card template belongs to
|
59
|
-
attr_reader :note_type
|
60
|
-
|
61
|
-
##
|
62
|
-
# 0 for the first card template of the note type, 1 for the second, etc.
|
63
|
-
attr_reader :ordinal_number
|
64
|
-
|
65
|
-
##
|
66
|
-
# Instantiates a new card template called +name+ for the given note type
|
67
|
-
#
|
68
|
-
def initialize(note_type:, name: nil, args: nil)
|
69
|
-
raise ArgumentError unless (name && args.nil?) || (args && args["name"])
|
70
|
-
|
71
|
-
@note_type = note_type
|
72
|
-
if args
|
73
|
-
setup_card_template_instance_variables_from_existing(args: args)
|
74
|
-
else
|
75
|
-
setup_card_template_instance_variables(name: name)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
def setup_card_template_instance_variables_from_existing(args:)
|
82
|
-
@name = args["name"]
|
83
|
-
@ordinal_number = args["ord"]
|
84
|
-
@question_format = args["qfmt"]
|
85
|
-
@answer_format = args["afmt"]
|
86
|
-
@bqfmt = args["bqfmt"]
|
87
|
-
@bafmt = args["bafmt"]
|
88
|
-
@deck_id = args["did"]
|
89
|
-
@browser_font_style = args["bfont"]
|
90
|
-
@browser_font_size = args["bsize"]
|
91
|
-
end
|
92
|
-
|
93
|
-
def setup_card_template_instance_variables(name:)
|
94
|
-
@name = name
|
95
|
-
@ordinal_number = @note_type.card_templates.length
|
96
|
-
@question_format = ""
|
97
|
-
@answer_format = ""
|
98
|
-
@bqfmt = ""
|
99
|
-
@bafmt = ""
|
100
|
-
@deck_id = nil
|
101
|
-
@browser_font_style = ""
|
102
|
-
@browser_font_size = 0
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
@@ -1,105 +0,0 @@
|
|
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
|
-
# The instance of AnkiRecord::AnkiPackage that this collection object belongs to
|
19
|
-
attr_reader :anki_package
|
20
|
-
|
21
|
-
##
|
22
|
-
# The id attribute will become, or is the same as, the primary key id of this record in the database
|
23
|
-
#
|
24
|
-
# Since there should be only one col record, this attribute should be 1
|
25
|
-
attr_reader :id
|
26
|
-
|
27
|
-
##
|
28
|
-
# The time in milliseconds that the col record was created since the 1970 epoch
|
29
|
-
attr_reader :creation_timestamp
|
30
|
-
|
31
|
-
##
|
32
|
-
# The last time that the col record was modified in milliseconds since the 1970 epoch
|
33
|
-
attr_reader :last_modified_time
|
34
|
-
|
35
|
-
##
|
36
|
-
# An array of the collection's note type objects, which are instances of AnkiRecord::NoteType
|
37
|
-
attr_reader :note_types
|
38
|
-
|
39
|
-
##
|
40
|
-
# An array of the collection's deck objects, which are instances of AnkiRecord::Deck
|
41
|
-
attr_reader :decks
|
42
|
-
|
43
|
-
##
|
44
|
-
# An array of the collection's deck options group objects, which are instances of AnkiRecord::DeckOptionsGroup
|
45
|
-
#
|
46
|
-
# These represent groups of settings that can be applied to a deck.
|
47
|
-
attr_reader :deck_options_groups
|
48
|
-
|
49
|
-
##
|
50
|
-
# Instantiates the collection object for the +anki_package+
|
51
|
-
#
|
52
|
-
# The collection object represents the single record of the collection.anki21 database col table.
|
53
|
-
#
|
54
|
-
# This record stores the note types used by the notes and the decks that they belong to.
|
55
|
-
def initialize(anki_package:)
|
56
|
-
setup_collection_instance_variables(anki_package: anki_package)
|
57
|
-
end
|
58
|
-
|
59
|
-
##
|
60
|
-
# Find one of the collection's note types by name
|
61
|
-
def find_note_type_by(name: nil)
|
62
|
-
note_types.find { |note_type| note_type.name == name }
|
63
|
-
end
|
64
|
-
|
65
|
-
##
|
66
|
-
# Find one of the collection's decks by name
|
67
|
-
def find_deck_by(name: nil)
|
68
|
-
decks.find { |deck| deck.name == name }
|
69
|
-
end
|
70
|
-
|
71
|
-
private
|
72
|
-
|
73
|
-
# rubocop:disable Metrics/MethodLength
|
74
|
-
# rubocop:disable Metrics/AbcSize
|
75
|
-
def setup_collection_instance_variables(anki_package:)
|
76
|
-
@anki_package = anki_package
|
77
|
-
@id = col_record["id"]
|
78
|
-
@creation_timestamp = col_record["crt"]
|
79
|
-
@last_modified_time = col_record["mod"]
|
80
|
-
@scm = col_record["scm"]
|
81
|
-
@ver = col_record["ver"]
|
82
|
-
@dty = col_record["dty"]
|
83
|
-
@usn = col_record["usn"]
|
84
|
-
@ls = col_record["ls"]
|
85
|
-
@configuration = JSON.parse(col_record["conf"])
|
86
|
-
@note_types = JSON.parse(col_record["models"]).values.map do |model_hash|
|
87
|
-
NoteType.new(collection: self, args: model_hash)
|
88
|
-
end
|
89
|
-
@decks = JSON.parse(col_record["decks"]).values.map do |deck_hash|
|
90
|
-
Deck.new(collection: self, args: deck_hash)
|
91
|
-
end
|
92
|
-
@deck_options_groups = JSON.parse(col_record["dconf"]).values.map do |dconf_hash|
|
93
|
-
DeckOptionsGroup.new(collection: self, args: dconf_hash)
|
94
|
-
end
|
95
|
-
@tags = JSON.parse(col_record["tags"])
|
96
|
-
remove_instance_variable(:@col_record)
|
97
|
-
end
|
98
|
-
# rubocop:enable Metrics/AbcSize
|
99
|
-
# rubocop:enable Metrics/MethodLength
|
100
|
-
|
101
|
-
def col_record
|
102
|
-
@col_record ||= @anki_package.execute("select * from col;").first
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
@@ -1,77 +0,0 @@
|
|
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
|
@@ -1,10 +0,0 @@
|
|
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
|
@@ -1,10 +0,0 @@
|
|
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
|
data/lib/anki_record/deck.rb
DELETED
@@ -1,101 +0,0 @@
|
|
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
|
-
# Deck represents an Anki deck
|
11
|
-
class Deck
|
12
|
-
include SharedConstantsHelper
|
13
|
-
include TimeHelper
|
14
|
-
|
15
|
-
DEFAULT_DECK_TODAY_ARRAY = [0, 0].freeze
|
16
|
-
DEFAULT_COLLAPSED = false
|
17
|
-
|
18
|
-
private_constant :DEFAULT_DECK_TODAY_ARRAY, :DEFAULT_COLLAPSED
|
19
|
-
|
20
|
-
##
|
21
|
-
# The collection object that the deck belongs to
|
22
|
-
attr_reader :collection
|
23
|
-
|
24
|
-
##
|
25
|
-
# The name of the deck
|
26
|
-
attr_accessor :name
|
27
|
-
|
28
|
-
##
|
29
|
-
# The description of the deck
|
30
|
-
attr_accessor :description
|
31
|
-
|
32
|
-
##
|
33
|
-
# The id of the deck
|
34
|
-
attr_reader :id
|
35
|
-
|
36
|
-
##
|
37
|
-
# The last time the deck was modified in number of seconds since the epoch
|
38
|
-
#
|
39
|
-
# TODO: is this really supposed to be seconds? Should it be milliseconds?
|
40
|
-
attr_reader :last_modified_time
|
41
|
-
|
42
|
-
##
|
43
|
-
# The id of the eck options/settings group that is applied to the deck
|
44
|
-
attr_reader :deck_options_group_id
|
45
|
-
|
46
|
-
##
|
47
|
-
# Instantiates a new Deck object
|
48
|
-
def initialize(collection:, name: nil, args: nil)
|
49
|
-
raise ArgumentError unless (name && args.nil?) || (args && args["name"])
|
50
|
-
|
51
|
-
@collection = collection
|
52
|
-
if args
|
53
|
-
setup_deck_instance_variables_from_existing(args: args)
|
54
|
-
else
|
55
|
-
setup_deck_instance_variables(name: name)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
# rubocop:disable Metrics/MethodLength
|
62
|
-
# rubocop:disable Metrics/AbcSize
|
63
|
-
def setup_deck_instance_variables_from_existing(args:)
|
64
|
-
@id = args["id"]
|
65
|
-
@last_modified_time = args["mod"]
|
66
|
-
@name = args["name"]
|
67
|
-
@usn = args["usn"]
|
68
|
-
@learn_today = args["lrnToday"]
|
69
|
-
@review_today = args["revToday"]
|
70
|
-
@new_today = args["newToday"]
|
71
|
-
@time_today = args["timeToday"]
|
72
|
-
@collapsed_in_main_window = args["collapsed"]
|
73
|
-
@collapsed_in_browser = args["browserCollapsed"]
|
74
|
-
@description = args["desc"]
|
75
|
-
@dyn = args["dyn"]
|
76
|
-
@deck_options_group_id = args["conf"]
|
77
|
-
@extend_new = args["extendNew"]
|
78
|
-
@extend_review = args["extendRev"]
|
79
|
-
end
|
80
|
-
# rubocop:enable Metrics/MethodLength
|
81
|
-
# rubocop:enable Metrics/AbcSize
|
82
|
-
|
83
|
-
# rubocop:disable Metrics/MethodLength
|
84
|
-
def setup_deck_instance_variables(name:)
|
85
|
-
@id = milliseconds_since_epoch
|
86
|
-
@last_modified_time = seconds_since_epoch
|
87
|
-
@name = name
|
88
|
-
@usn = NEW_OBJECT_USN
|
89
|
-
@learn_today = @review_today = @new_today = @time_today = DEFAULT_DECK_TODAY_ARRAY
|
90
|
-
@collapsed_in_main_window = DEFAULT_COLLAPSED
|
91
|
-
@collapsed_in_browser = DEFAULT_COLLAPSED
|
92
|
-
@description = ""
|
93
|
-
@dyn = NON_FILTERED_DECK_DYN
|
94
|
-
@deck_options_group_id = nil # TODO: Set id to the default deck options group?
|
95
|
-
# TODO: alternatively, if this is nil when the deck is saved, it can be set to the default options group id
|
96
|
-
@extend_new = 0
|
97
|
-
@extend_review = 0
|
98
|
-
end
|
99
|
-
# rubocop:enable Metrics/MethodLength
|
100
|
-
end
|
101
|
-
end
|
data/lib/anki_record/note.rb
DELETED
@@ -1,135 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "pry"
|
4
|
-
require "securerandom"
|
5
|
-
|
6
|
-
require_relative "helpers/checksum_helper"
|
7
|
-
require_relative "helpers/time_helper"
|
8
|
-
|
9
|
-
module AnkiRecord
|
10
|
-
##
|
11
|
-
# Note represents an Anki note
|
12
|
-
class Note
|
13
|
-
include ChecksumHelper
|
14
|
-
include TimeHelper
|
15
|
-
include SharedConstantsHelper
|
16
|
-
|
17
|
-
##
|
18
|
-
# The id of the note
|
19
|
-
attr_reader :id
|
20
|
-
|
21
|
-
##
|
22
|
-
# The globally unique id of the note
|
23
|
-
attr_reader :guid
|
24
|
-
|
25
|
-
##
|
26
|
-
# The last time the note was modified in seconds since the 1970 epoch
|
27
|
-
attr_reader :last_modified_time
|
28
|
-
|
29
|
-
##
|
30
|
-
# The tags applied to the note
|
31
|
-
#
|
32
|
-
# TODO: a setter method for the tags of the note
|
33
|
-
attr_reader :tags
|
34
|
-
|
35
|
-
##
|
36
|
-
# The deck that the note's cards will be put into when saved
|
37
|
-
attr_reader :deck
|
38
|
-
|
39
|
-
##
|
40
|
-
# The note type of the note
|
41
|
-
attr_reader :note_type
|
42
|
-
|
43
|
-
##
|
44
|
-
# The card objects of the note
|
45
|
-
attr_reader :cards
|
46
|
-
|
47
|
-
##
|
48
|
-
# Instantiate a new note for a deck and note type
|
49
|
-
# or TODO: instantiate a new object from an already existing record
|
50
|
-
# rubocop:disable Metrics/MethodLength
|
51
|
-
# rubocop:disable Metrics/AbcSize
|
52
|
-
def initialize(deck:, note_type:)
|
53
|
-
raise ArgumentError unless deck && note_type && deck.collection == note_type.collection
|
54
|
-
|
55
|
-
@apkg = deck.collection.anki_package
|
56
|
-
|
57
|
-
@id = milliseconds_since_epoch
|
58
|
-
@guid = globally_unique_id
|
59
|
-
@last_modified_time = seconds_since_epoch
|
60
|
-
@usn = NEW_OBJECT_USN
|
61
|
-
@tags = []
|
62
|
-
@deck = deck
|
63
|
-
@note_type = note_type
|
64
|
-
@field_contents = setup_field_contents
|
65
|
-
@cards = @note_type.card_templates.map { |card_template| Card.new(note: self, card_template: card_template) }
|
66
|
-
end
|
67
|
-
# rubocop:enable Metrics/MethodLength
|
68
|
-
# rubocop:enable Metrics/AbcSize
|
69
|
-
|
70
|
-
##
|
71
|
-
# Save the note to the collection.anki21 database
|
72
|
-
def save
|
73
|
-
@apkg.execute <<~SQL
|
74
|
-
insert into notes (id, guid, mid, mod, usn, tags, flds, sfld, csum, flags, data)
|
75
|
-
values ('#{@id}', '#{@guid}', '#{note_type.id}', '#{@last_modified_time}', '#{@usn}', '#{@tags.join(" ")}', '#{field_values_separated_by_us}', '#{sort_field_value}', '#{checksum(sort_field_value)}', '0', '')
|
76
|
-
SQL
|
77
|
-
cards.each(&:save)
|
78
|
-
true
|
79
|
-
end
|
80
|
-
|
81
|
-
##
|
82
|
-
# This overrides BasicObject#method_missing and has the effect of creating "ghost methods"
|
83
|
-
#
|
84
|
-
# Specifically, creates setter and getter ghost methods for the fields of the note's note type
|
85
|
-
#
|
86
|
-
# TODO: This should raise a NoMethodError if
|
87
|
-
# the missing method does not end with '=' and is not a field of the note type
|
88
|
-
def method_missing(method_name, field_content = nil)
|
89
|
-
method_name = method_name.to_s
|
90
|
-
return @field_contents[method_name] unless method_name.end_with?("=")
|
91
|
-
|
92
|
-
method_name = method_name.chomp("=")
|
93
|
-
valid_fields_snake_names = @field_contents.keys
|
94
|
-
unless valid_fields_snake_names.include?(method_name)
|
95
|
-
raise ArgumentError, "Valid fields for the #{note_type.name} note type are one of #{valid_fields_snake_names.join(", ")}"
|
96
|
-
end
|
97
|
-
|
98
|
-
@field_contents[method_name] = field_content
|
99
|
-
end
|
100
|
-
|
101
|
-
##
|
102
|
-
# This allows #respond_to? to be accurate for the ghost methods created by #method_missing
|
103
|
-
def respond_to_missing?(method_name, *)
|
104
|
-
method_name = method_name.to_s
|
105
|
-
if method_name.end_with?("=")
|
106
|
-
note_type.snake_case_field_names.include?(method_name.chomp("="))
|
107
|
-
else
|
108
|
-
note_type.snake_case_field_names.include?(method_name)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
private
|
113
|
-
|
114
|
-
def setup_field_contents
|
115
|
-
field_contents = {}
|
116
|
-
note_type.snake_case_field_names.each do |field_name|
|
117
|
-
field_contents[field_name] = ""
|
118
|
-
end
|
119
|
-
field_contents
|
120
|
-
end
|
121
|
-
|
122
|
-
def globally_unique_id
|
123
|
-
SecureRandom.uuid.slice(5...15)
|
124
|
-
end
|
125
|
-
|
126
|
-
def field_values_separated_by_us
|
127
|
-
# The ASCII control code represented by hexadecimal 1F is the Unit Separator (US)
|
128
|
-
note_type.snake_case_field_names.map { |field_name| @field_contents[field_name] }.join("\x1F")
|
129
|
-
end
|
130
|
-
|
131
|
-
def sort_field_value
|
132
|
-
@field_contents[note_type.snake_case_sort_field_name]
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|