anki_record 0.2.0 → 0.3.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +13 -2
  3. data/CHANGELOG.md +37 -9
  4. data/Gemfile +3 -1
  5. data/Gemfile.lock +10 -2
  6. data/README.md +120 -35
  7. data/anki_record.gemspec +1 -5
  8. data/lib/anki_record/anki_package/anki_package.rb +237 -0
  9. data/lib/anki_record/card/card.rb +108 -0
  10. data/lib/anki_record/card/card_attributes.rb +39 -0
  11. data/lib/anki_record/card_template/card_template.rb +64 -0
  12. data/lib/anki_record/card_template/card_template_attributes.rb +69 -0
  13. data/lib/anki_record/collection/collection.rb +182 -0
  14. data/lib/anki_record/collection/collection_attributes.rb +35 -0
  15. data/lib/anki_record/database_setup_constants.rb +88 -0
  16. data/lib/anki_record/deck/deck.rb +99 -0
  17. data/lib/anki_record/deck/deck_attributes.rb +30 -0
  18. data/lib/anki_record/deck/deck_defaults.rb +19 -0
  19. data/lib/anki_record/{deck_options_group.rb → deck_options_group/deck_options_group.rb} +12 -31
  20. data/lib/anki_record/deck_options_group/deck_options_group_attributes.rb +23 -0
  21. data/lib/anki_record/helpers/checksum_helper.rb +10 -11
  22. data/lib/anki_record/helpers/data_query_helper.rb +15 -0
  23. data/lib/anki_record/helpers/shared_constants_helper.rb +6 -6
  24. data/lib/anki_record/helpers/time_helper.rb +18 -13
  25. data/lib/anki_record/note/note.rb +178 -0
  26. data/lib/anki_record/note/note_attributes.rb +56 -0
  27. data/lib/anki_record/note_field/note_field.rb +62 -0
  28. data/lib/anki_record/note_field/note_field_attributes.rb +39 -0
  29. data/lib/anki_record/note_field/note_field_defaults.rb +19 -0
  30. data/lib/anki_record/note_type/note_type.rb +161 -0
  31. data/lib/anki_record/note_type/note_type_attributes.rb +80 -0
  32. data/lib/anki_record/note_type/note_type_defaults.rb +38 -0
  33. data/lib/anki_record/version.rb +1 -1
  34. data/lib/anki_record.rb +1 -16
  35. metadata +26 -16
  36. data/lib/anki_record/anki_package.rb +0 -194
  37. data/lib/anki_record/card.rb +0 -75
  38. data/lib/anki_record/card_template.rb +0 -105
  39. data/lib/anki_record/collection.rb +0 -105
  40. data/lib/anki_record/db/anki_schema_definition.rb +0 -77
  41. data/lib/anki_record/db/clean_collection21_record.rb +0 -10
  42. data/lib/anki_record/db/clean_collection2_record.rb +0 -10
  43. data/lib/anki_record/deck.rb +0 -101
  44. data/lib/anki_record/note.rb +0 -135
  45. data/lib/anki_record/note_field.rb +0 -84
  46. data/lib/anki_record/note_type.rb +0 -233
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../helpers/shared_constants_helper"
4
+ require_relative "../helpers/time_helper"
5
+ require_relative "card_attributes"
6
+
7
+ module AnkiRecord
8
+ ##
9
+ # Card represents an Anki card.
10
+ class Card
11
+ include CardAttributes
12
+ include Helpers::TimeHelper
13
+ include Helpers::SharedConstantsHelper
14
+
15
+ def initialize(note:, card_template: nil, card_data: nil) # :nodoc:
16
+ @note = note
17
+ if card_template
18
+ setup_instance_variables_for_new_card(card_template: card_template)
19
+ elsif card_data
20
+ setup_instance_variables_from_existing(card_data: card_data)
21
+ else
22
+ raise ArgumentError
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def setup_instance_variables_for_new_card(card_template:)
29
+ raise ArgumentError unless @note.note_type == card_template.note_type
30
+
31
+ setup_collaborator_object_instance_variables_for_new_card(card_template: card_template)
32
+ setup_simple_instance_variables_for_new_card
33
+ end
34
+
35
+ def setup_collaborator_object_instance_variables_for_new_card(card_template:)
36
+ @card_template = card_template
37
+ @deck = @note.deck
38
+ @collection = @deck.collection
39
+ end
40
+
41
+ def setup_simple_instance_variables_for_new_card
42
+ @id = milliseconds_since_epoch
43
+ @last_modified_timestamp = seconds_since_epoch
44
+ @usn = NEW_OBJECT_USN
45
+ %w[type queue due ivl factor reps lapses left odue odid flags].each do |instance_variable_name|
46
+ instance_variable_set "@#{instance_variable_name}", 0
47
+ end
48
+ @data = "{}"
49
+ end
50
+
51
+ def setup_instance_variables_from_existing(card_data:)
52
+ setup_collaborator_object_instance_variables_from_existing(card_data: card_data)
53
+ setup_simple_instance_variables_from_existing(card_data: card_data)
54
+ end
55
+
56
+ def setup_collaborator_object_instance_variables_from_existing(card_data:)
57
+ @collection = note.note_type.collection
58
+ @deck = collection.find_deck_by id: card_data["did"]
59
+ @card_template = note.note_type.card_templates.find do |card_template|
60
+ card_template.ordinal_number == card_data["ord"]
61
+ end
62
+ end
63
+
64
+ def setup_simple_instance_variables_from_existing(card_data:)
65
+ @last_modified_timestamp = card_data["mod"]
66
+ %w[id usn type queue due ivl factor reps lapses left odue odid flags data].each do |instance_variable_name|
67
+ instance_variable_set "@#{instance_variable_name}", card_data[instance_variable_name]
68
+ end
69
+ end
70
+
71
+ public
72
+
73
+ def save(note_exists_already: false) # :nodoc:
74
+ note_exists_already ? update_card_in_collection_anki21 : insert_new_card_in_collection_anki21
75
+ end
76
+
77
+ private
78
+
79
+ def update_card_in_collection_anki21
80
+ statement = @collection.anki_package.prepare <<~SQL
81
+ update cards set nid = ?, did = ?, ord = ?, mod = ?, usn = ?, type = ?,
82
+ queue = ?, due = ?, ivl = ?, factor = ?, reps = ?, lapses = ?,
83
+ left = ?, odue = ?, odid = ?, flags = ?, data = ? where id = ?
84
+ SQL
85
+ statement.execute [@note.id, @deck.id, ordinal_number,
86
+ @last_modified_timestamp, @usn, @type, @queue,
87
+ @due, @ivl, @factor, @reps,
88
+ @lapses, @left, @odue, @odid, @flags, @data, @id]
89
+ end
90
+
91
+ def insert_new_card_in_collection_anki21
92
+ statement = @collection.anki_package.prepare <<~SQL
93
+ insert into cards (id, nid, did, ord,
94
+ mod, usn, type, queue,
95
+ due, ivl, factor, reps,
96
+ lapses, left, odue, odid, flags, data)
97
+ values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
98
+ SQL
99
+ statement.execute [@id, @note.id, @deck.id, ordinal_number,
100
+ @last_modified_timestamp, @usn, @type, @queue,
101
+ @due, @ivl, @factor, @reps, @lapses, @left, @odue, @odid, @flags, @data]
102
+ end
103
+
104
+ def ordinal_number
105
+ @card_template&.ordinal_number
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnkiRecord
4
+ ##
5
+ # Module with the Card class's attribute readers, writers, and accessors.
6
+ module CardAttributes
7
+ ##
8
+ # The card's note object.
9
+ attr_reader :note
10
+
11
+ ##
12
+ # The card's deck object.
13
+ attr_reader :deck
14
+
15
+ ##
16
+ # The card's collection object.
17
+ attr_reader :collection
18
+
19
+ ##
20
+ # The card's card template object.
21
+ attr_reader :card_template
22
+
23
+ ##
24
+ # The card's id.
25
+ #
26
+ # This is also the number of milliseconds since the 1970 epoch at which the card was created.
27
+ attr_reader :id
28
+
29
+ ##
30
+ # The number of seconds since the 1970 epoch at which the card was last modified.
31
+ attr_reader :last_modified_timestamp
32
+
33
+ ##
34
+ # The card's update sequence number.
35
+ attr_reader :usn
36
+
37
+ attr_reader :type, :queue, :due, :ivl, :factor, :reps, :lapses, :left, :odue, :odid, :flags, :data
38
+ end
39
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "card_template_attributes"
4
+
5
+ module AnkiRecord
6
+ ##
7
+ # CardTemplate represents a card template of an Anki note type.
8
+ class CardTemplate
9
+ include CardTemplateAttributes
10
+
11
+ # Instantiates a new card template with name +name+ for the note type +note_type+.
12
+ def initialize(note_type:, name: nil, args: nil)
13
+ raise ArgumentError unless (name && args.nil?) || (args && args["name"])
14
+
15
+ @note_type = note_type
16
+ if args
17
+ setup_card_template_instance_variables_from_existing(args: args)
18
+ else
19
+ setup_card_template_instance_variables(name: name)
20
+ end
21
+
22
+ @note_type.add_card_template self
23
+ end
24
+
25
+ def to_h # :nodoc:
26
+ {
27
+ name: @name,
28
+ ord: @ordinal_number,
29
+ qfmt: @question_format, afmt: @answer_format,
30
+ bqfmt: @bqfmt,
31
+ bafmt: @bafmt,
32
+ did: @deck_id,
33
+ bfont: @browser_font_style,
34
+ bsize: @browser_font_size
35
+ }
36
+ end
37
+
38
+ private
39
+
40
+ def setup_card_template_instance_variables_from_existing(args:)
41
+ @name = args["name"]
42
+ @ordinal_number = args["ord"]
43
+ @question_format = args["qfmt"]
44
+ @answer_format = args["afmt"]
45
+ @bqfmt = args["bqfmt"]
46
+ @bafmt = args["bafmt"]
47
+ @deck_id = args["did"]
48
+ @browser_font_style = args["bfont"]
49
+ @browser_font_size = args["bsize"]
50
+ end
51
+
52
+ def setup_card_template_instance_variables(name:)
53
+ @name = name
54
+ @ordinal_number = @note_type.card_templates.length
55
+ @question_format = ""
56
+ @answer_format = ""
57
+ @bqfmt = ""
58
+ @bafmt = ""
59
+ @deck_id = nil
60
+ @browser_font_style = ""
61
+ @browser_font_size = 0
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnkiRecord
4
+ ##
5
+ # Module with the CardTemplate class's attribute readers, writers, and accessors.
6
+ module CardTemplateAttributes
7
+ ##
8
+ # The card template's name.
9
+ attr_accessor :name
10
+
11
+ ##
12
+ # The card template's font style in the browser.
13
+ attr_accessor :browser_font_style
14
+
15
+ ##
16
+ # The card template's font size used in the browser.
17
+ attr_accessor :browser_font_size
18
+
19
+ ##
20
+ # The card template's question format.
21
+ attr_reader :question_format
22
+
23
+ ##
24
+ # Sets the question format of the card template.
25
+ #
26
+ # Raises an ArgumentError if the specified format attempts to use invalid fields.
27
+ def question_format=(format)
28
+ fields_in_specified_format = format.scan(/{{.+?}}/).map do |capture|
29
+ capture.chomp("}}").reverse.chomp("{{").reverse
30
+ end
31
+ if fields_in_specified_format.any? do |field_name|
32
+ !note_type.allowed_card_template_question_format_field_names.include?(field_name)
33
+ end
34
+ raise ArgumentError, "You tried to use a field that the note type does not have."
35
+ end
36
+
37
+ @question_format = format
38
+ end
39
+
40
+ ##
41
+ # The card template's answer format.
42
+ attr_reader :answer_format
43
+
44
+ ##
45
+ # Sets the answer format of the card template.
46
+ #
47
+ # Raises an ArgumentError if the specified format attempts to use invalid fields.
48
+ def answer_format=(format)
49
+ fields_in_specified_format = format.scan(/{{.+?}}/).map do |capture|
50
+ capture.chomp("}}").reverse.chomp("{{").reverse
51
+ end
52
+ if fields_in_specified_format.any? do |field_name|
53
+ !note_type.allowed_card_template_answer_format_field_names.include?(field_name)
54
+ end
55
+ raise ArgumentError, "You tried to use a field that the note type does not have."
56
+ end
57
+
58
+ @answer_format = format
59
+ end
60
+
61
+ ##
62
+ # The card template's note type object.
63
+ attr_reader :note_type
64
+
65
+ ##
66
+ # 0 for the first card template of the note type, 1 for the second, etc.
67
+ attr_reader :ordinal_number
68
+ end
69
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ require_relative "../deck/deck"
6
+ require_relative "../deck_options_group/deck_options_group"
7
+ require_relative "../helpers/data_query_helper"
8
+ require_relative "../helpers/time_helper"
9
+ require_relative "../note_type/note_type"
10
+ require_relative "collection_attributes"
11
+
12
+ module AnkiRecord
13
+ ##
14
+ # Collection represents the single record in the Anki collection.anki21 database's `col` table.
15
+ # The note types, decks, and deck options groups data are contained within this record.
16
+ class Collection
17
+ include Helpers::DataQueryHelper
18
+ include Helpers::TimeHelper
19
+ include CollectionAttributes
20
+
21
+ def initialize(anki_package:) # :nodoc:
22
+ setup_collection_instance_variables(anki_package: anki_package)
23
+ end
24
+
25
+ def add_note_type(note_type) # :nodoc:
26
+ raise ArgumentError unless note_type.instance_of?(AnkiRecord::NoteType)
27
+
28
+ existing_note_type = nil
29
+ @note_types.each do |nt|
30
+ existing_note_type = nt if nt.id == note_type.id
31
+ end
32
+ @note_types.delete(existing_note_type) if existing_note_type
33
+
34
+ @note_types << note_type
35
+ end
36
+
37
+ def add_deck(deck) # :nodoc:
38
+ raise ArgumentError unless deck.instance_of?(AnkiRecord::Deck)
39
+
40
+ @decks << deck
41
+ end
42
+
43
+ def add_deck_options_group(deck_options_group) # :nodoc:
44
+ raise ArgumentError unless deck_options_group.instance_of?(AnkiRecord::DeckOptionsGroup)
45
+
46
+ @deck_options_groups << deck_options_group
47
+ end
48
+
49
+ ##
50
+ # Returns the collection's note type object found by either +name+ or +id+, or nil if it is not found.
51
+ def find_note_type_by(name: nil, id: nil)
52
+ if (id && name) || (id.nil? && name.nil?)
53
+ raise ArgumentError,
54
+ "You must pass either an id or name keyword argument."
55
+ end
56
+
57
+ name ? find_note_type_by_name(name: name) : find_note_type_by_id(id: id)
58
+ end
59
+
60
+ private
61
+
62
+ def find_note_type_by_name(name:)
63
+ note_types.find { |note_type| note_type.name == name }
64
+ end
65
+
66
+ def find_note_type_by_id(id:)
67
+ note_types.find { |note_type| note_type.id == id }
68
+ end
69
+
70
+ public
71
+
72
+ ##
73
+ # Returns the collection's deck object found by either +name+ or +id+, or nil if it is not found.
74
+ def find_deck_by(name: nil, id: nil)
75
+ if (id && name) || (id.nil? && name.nil?)
76
+ raise ArgumentError,
77
+ "You must pass either an id or name keyword argument."
78
+ end
79
+
80
+ name ? find_deck_by_name(name: name) : find_deck_by_id(id: id)
81
+ end
82
+
83
+ private
84
+
85
+ def find_deck_by_name(name:)
86
+ decks.find { |deck| deck.name == name }
87
+ end
88
+
89
+ def find_deck_by_id(id:)
90
+ decks.find { |deck| deck.id == id }
91
+ end
92
+
93
+ public
94
+
95
+ ##
96
+ # Returns the collection's deck options group object found by +id+, or nil if it is not found.
97
+ def find_deck_options_group_by(id:)
98
+ deck_options_groups.find { |deck_options_group| deck_options_group.id == id }
99
+ end
100
+
101
+ ##
102
+ # Returns the collection's note object found by +id+, or nil if it is not found.
103
+ def find_note_by(id:)
104
+ note_cards_data = note_cards_data_for_note_id sql_able: anki_package, id: id
105
+ return nil unless note_cards_data
106
+
107
+ AnkiRecord::Note.new collection: self, data: note_cards_data
108
+ end
109
+
110
+ # :nodoc:
111
+ def decks_json
112
+ JSON.parse(anki_package.prepare("select decks from col;").execute.first["decks"])
113
+ end
114
+
115
+ # :nodoc:
116
+ def models_json
117
+ JSON.parse(anki_package.prepare("select models from col;").execute.first["models"])
118
+ end
119
+
120
+ def copy_over_existing(col_record:) # :nodoc:
121
+ @col_record = col_record
122
+ setup_simple_collaborator_objects
123
+ setup_custom_collaborator_objects
124
+ remove_instance_variable(:@col_record)
125
+ end
126
+
127
+ private
128
+
129
+ def setup_collection_instance_variables(anki_package:)
130
+ @anki_package = anki_package
131
+ setup_simple_collaborator_objects
132
+ setup_custom_collaborator_objects
133
+ remove_instance_variable(:@col_record)
134
+ end
135
+
136
+ def col_record
137
+ @col_record ||= @anki_package.prepare("select * from col").execute.first
138
+ end
139
+
140
+ # rubocop:disable Metrics/AbcSize
141
+ def setup_simple_collaborator_objects
142
+ @id = col_record["id"]
143
+ @created_at_timestamp = col_record["crt"]
144
+ @last_modified_timestamp = col_record["mod"]
145
+ @scm = col_record["scm"]
146
+ @ver = col_record["ver"]
147
+ @dty = col_record["dty"]
148
+ @usn = col_record["usn"]
149
+ @ls = col_record["ls"]
150
+ @configuration = JSON.parse(col_record["conf"])
151
+ @tags = JSON.parse(col_record["tags"])
152
+ end
153
+ # rubocop:enable Metrics/AbcSize
154
+
155
+ def setup_custom_collaborator_objects
156
+ setup_note_type_collaborators
157
+ setup_deck_options_groups_collaborators
158
+ setup_deck_collaborators
159
+ end
160
+
161
+ def setup_note_type_collaborators
162
+ @note_types = []
163
+ JSON.parse(col_record["models"]).values.map do |model_hash|
164
+ NoteType.new(collection: self, args: model_hash)
165
+ end
166
+ end
167
+
168
+ def setup_deck_collaborators
169
+ @decks = []
170
+ JSON.parse(col_record["decks"]).values.map do |deck_hash|
171
+ Deck.new(collection: self, args: deck_hash)
172
+ end
173
+ end
174
+
175
+ def setup_deck_options_groups_collaborators
176
+ @deck_options_groups = []
177
+ JSON.parse(col_record["dconf"]).values.map do |dconf_hash|
178
+ DeckOptionsGroup.new(collection: self, args: dconf_hash)
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnkiRecord
4
+ ##
5
+ # Module with the Collection class's attribute readers, writers, and accessors.
6
+ module CollectionAttributes
7
+ ##
8
+ # The collection's Anki package object.
9
+ attr_reader :anki_package
10
+
11
+ ##
12
+ # The collection's id, which is also the id of the col record in the collection.anki21 database (usually 1).
13
+ attr_reader :id
14
+
15
+ ##
16
+ # The number of milliseconds since the 1970 epoch when the collection record was created.
17
+ attr_reader :created_at_timestamp
18
+
19
+ ##
20
+ # The number of milliseconds since the 1970 epoch at which the collection record was last modified.
21
+ attr_reader :last_modified_timestamp
22
+
23
+ ##
24
+ # The collection's note type objects as an array.
25
+ attr_reader :note_types
26
+
27
+ ##
28
+ # The collection's deck objects as an array
29
+ attr_reader :decks
30
+
31
+ ##
32
+ # The collection's deck option group objects as an array.
33
+ attr_reader :deck_options_groups
34
+ end
35
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnkiRecord
4
+ # :nodoc:
5
+ ANKI_SCHEMA_DEFINITION = <<~SQL
6
+ CREATE TABLE col (
7
+ id integer PRIMARY KEY,
8
+ crt integer NOT NULL,
9
+ mod integer NOT NULL,
10
+ scm integer NOT NULL,
11
+ ver integer NOT NULL,
12
+ dty integer NOT NULL,
13
+ usn integer NOT NULL,
14
+ ls integer NOT NULL,
15
+ conf text NOT NULL,
16
+ models text NOT NULL,
17
+ decks text NOT NULL,
18
+ dconf text NOT NULL,
19
+ tags text NOT NULL
20
+ );
21
+ CREATE TABLE notes (
22
+ id integer PRIMARY KEY,
23
+ guid text NOT NULL,
24
+ mid integer NOT NULL,
25
+ mod integer NOT NULL,
26
+ usn integer NOT NULL,
27
+ tags text NOT NULL,
28
+ flds text NOT NULL,
29
+ sfld integer NOT NULL,
30
+ csum integer NOT NULL,
31
+ flags integer NOT NULL,
32
+ data text NOT NULL
33
+ );
34
+ CREATE TABLE cards (
35
+ id integer PRIMARY KEY,
36
+ nid integer NOT NULL,
37
+ did integer NOT NULL,
38
+ ord integer NOT NULL,
39
+ mod integer NOT NULL,
40
+ usn integer NOT NULL,
41
+ type integer NOT NULL,
42
+ queue integer NOT NULL,
43
+ due integer NOT NULL,
44
+ ivl integer NOT NULL,
45
+ factor integer NOT NULL,
46
+ reps integer NOT NULL,
47
+ lapses integer NOT NULL,
48
+ left integer NOT NULL,
49
+ odue integer NOT NULL,
50
+ odid integer NOT NULL,
51
+ flags integer NOT NULL,
52
+ data text NOT NULL
53
+ );
54
+ CREATE TABLE revlog (
55
+ id integer PRIMARY KEY,
56
+ cid integer NOT NULL,
57
+ usn integer NOT NULL,
58
+ ease integer NOT NULL,
59
+ ivl integer NOT NULL,
60
+ lastIvl integer NOT NULL,
61
+ factor integer NOT NULL,
62
+ time integer NOT NULL,
63
+ type integer NOT NULL
64
+ );
65
+ CREATE INDEX ix_notes_usn ON notes (usn);
66
+ CREATE INDEX ix_cards_usn ON cards (usn);
67
+ CREATE INDEX ix_revlog_usn ON revlog (usn);
68
+ CREATE INDEX ix_cards_nid ON cards (nid);
69
+ CREATE INDEX ix_cards_sched ON cards (did, queue, due);
70
+ CREATE INDEX ix_revlog_cid ON revlog (cid);
71
+ CREATE INDEX ix_notes_csum ON notes (csum);
72
+ CREATE TABLE graves (
73
+ usn integer NOT NULL,
74
+ oid integer NOT NULL,
75
+ type integer NOT NULL
76
+ );
77
+ SQL
78
+
79
+ # :nodoc:
80
+ INSERT_COLLECTION_ANKI_2_COL_RECORD = <<~SQL
81
+ 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}}','{}');
82
+ SQL
83
+
84
+ # :nodoc:
85
+ INSERT_COLLECTION_ANKI_21_COL_RECORD = <<~SQL
86
+ 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}}','{}');
87
+ SQL
88
+ end