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