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.
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
@@ -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
@@ -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
@@ -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