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.
- checksums.yaml +4 -4
- data/.rubocop.yml +13 -2
- data/CHANGELOG.md +37 -9
- data/Gemfile +3 -1
- data/Gemfile.lock +10 -2
- data/README.md +120 -35
- data/anki_record.gemspec +1 -5
- data/lib/anki_record/anki_package/anki_package.rb +237 -0
- data/lib/anki_record/card/card.rb +108 -0
- data/lib/anki_record/card/card_attributes.rb +39 -0
- data/lib/anki_record/card_template/card_template.rb +64 -0
- data/lib/anki_record/card_template/card_template_attributes.rb +69 -0
- data/lib/anki_record/collection/collection.rb +182 -0
- data/lib/anki_record/collection/collection_attributes.rb +35 -0
- data/lib/anki_record/database_setup_constants.rb +88 -0
- data/lib/anki_record/deck/deck.rb +99 -0
- data/lib/anki_record/deck/deck_attributes.rb +30 -0
- data/lib/anki_record/deck/deck_defaults.rb +19 -0
- data/lib/anki_record/{deck_options_group.rb → deck_options_group/deck_options_group.rb} +12 -31
- data/lib/anki_record/deck_options_group/deck_options_group_attributes.rb +23 -0
- data/lib/anki_record/helpers/checksum_helper.rb +10 -11
- data/lib/anki_record/helpers/data_query_helper.rb +15 -0
- data/lib/anki_record/helpers/shared_constants_helper.rb +6 -6
- data/lib/anki_record/helpers/time_helper.rb +18 -13
- data/lib/anki_record/note/note.rb +178 -0
- data/lib/anki_record/note/note_attributes.rb +56 -0
- data/lib/anki_record/note_field/note_field.rb +62 -0
- data/lib/anki_record/note_field/note_field_attributes.rb +39 -0
- data/lib/anki_record/note_field/note_field_defaults.rb +19 -0
- data/lib/anki_record/note_type/note_type.rb +161 -0
- data/lib/anki_record/note_type/note_type_attributes.rb +80 -0
- data/lib/anki_record/note_type/note_type_defaults.rb +38 -0
- data/lib/anki_record/version.rb +1 -1
- data/lib/anki_record.rb +1 -16
- metadata +26 -16
- data/lib/anki_record/anki_package.rb +0 -194
- data/lib/anki_record/card.rb +0 -75
- data/lib/anki_record/card_template.rb +0 -105
- data/lib/anki_record/collection.rb +0 -105
- data/lib/anki_record/db/anki_schema_definition.rb +0 -77
- data/lib/anki_record/db/clean_collection21_record.rb +0 -10
- data/lib/anki_record/db/clean_collection2_record.rb +0 -10
- data/lib/anki_record/deck.rb +0 -101
- data/lib/anki_record/note.rb +0 -135
- data/lib/anki_record/note_field.rb +0 -84
- data/lib/anki_record/note_type.rb +0 -233
data/lib/anki_record/card.rb
DELETED
@@ -1,75 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "pry"
|
4
|
-
|
5
|
-
require_relative "helpers/shared_constants_helper"
|
6
|
-
require_relative "helpers/time_helper"
|
7
|
-
|
8
|
-
module AnkiRecord
|
9
|
-
##
|
10
|
-
# Card represents an Anki card.
|
11
|
-
class Card
|
12
|
-
include TimeHelper
|
13
|
-
include SharedConstantsHelper
|
14
|
-
|
15
|
-
##
|
16
|
-
# The note that the card belongs to
|
17
|
-
attr_reader :note
|
18
|
-
|
19
|
-
##
|
20
|
-
# The card template that the card uses
|
21
|
-
attr_reader :card_template
|
22
|
-
|
23
|
-
##
|
24
|
-
# The id of the card, which is time it was created as the number of milliseconds since the 1970 epoch
|
25
|
-
attr_reader :id
|
26
|
-
|
27
|
-
##
|
28
|
-
# The time that the card was last modified as the number of seconds since the 1970 epoch
|
29
|
-
attr_reader :last_modified_time
|
30
|
-
|
31
|
-
# rubocop:disable Metrics/MethodLength
|
32
|
-
# rubocop:disable Metrics/AbcSize
|
33
|
-
def initialize(note:, card_template:)
|
34
|
-
raise ArgumentError unless note && card_template && note.note_type == card_template.note_type
|
35
|
-
|
36
|
-
@note = note
|
37
|
-
@apkg = @note.deck.collection.anki_package
|
38
|
-
|
39
|
-
@card_template = card_template
|
40
|
-
|
41
|
-
@id = milliseconds_since_epoch
|
42
|
-
@last_modified_time = seconds_since_epoch
|
43
|
-
@usn = NEW_OBJECT_USN
|
44
|
-
@type = 0
|
45
|
-
@queue = 0
|
46
|
-
@due = 0
|
47
|
-
@ivl = 0
|
48
|
-
@factor = 0
|
49
|
-
@reps = 0
|
50
|
-
@lapses = 0
|
51
|
-
@left = 0
|
52
|
-
@odue = 0
|
53
|
-
@odid = 0
|
54
|
-
@flags = 0
|
55
|
-
@data = {}
|
56
|
-
end
|
57
|
-
# rubocop:enable Metrics/MethodLength
|
58
|
-
# rubocop:enable Metrics/AbcSize
|
59
|
-
|
60
|
-
##
|
61
|
-
# Saves the card to the collection.anki21 database
|
62
|
-
def save
|
63
|
-
@apkg.execute <<~SQL
|
64
|
-
insert into cards (id, nid, did, ord,
|
65
|
-
mod, usn, type, queue,
|
66
|
-
due, ivl, factor, reps,
|
67
|
-
lapses, left, odue, odid, flags, data)
|
68
|
-
values ('#{@id}', '#{@note.id}', '#{@note.deck.id}', '#{@card_template.ordinal_number}',
|
69
|
-
'#{@last_modified_time}', '#{@usn}', '#{@type}', '#{@queue}',
|
70
|
-
'#{@due}', '#{@ivl}', '#{@factor}', '#{@reps}',
|
71
|
-
'#{@lapses}', '#{@left}', '#{@odue}', '#{@odid}', '#{@flags}', '#{@data}')
|
72
|
-
SQL
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
@@ -1,105 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "pry"
|
4
|
-
|
5
|
-
# TODO: All instance variables should at least be readable
|
6
|
-
|
7
|
-
module AnkiRecord
|
8
|
-
##
|
9
|
-
# CardTemplate represents a card template of an Anki note type
|
10
|
-
class CardTemplate
|
11
|
-
##
|
12
|
-
# The name of the card template
|
13
|
-
attr_accessor :name
|
14
|
-
|
15
|
-
##
|
16
|
-
# The font style shown for the card template in the browser
|
17
|
-
attr_accessor :browser_font_style
|
18
|
-
|
19
|
-
##
|
20
|
-
# The font size used for the card template in the browser
|
21
|
-
attr_accessor :browser_font_size
|
22
|
-
|
23
|
-
##
|
24
|
-
# The question format of the card template
|
25
|
-
attr_reader :question_format
|
26
|
-
|
27
|
-
##
|
28
|
-
# Sets the question format and raises an ArgumentError if the specified format uses invalid fields
|
29
|
-
def question_format=(format)
|
30
|
-
fields_in_specified_format = format.scan(/{{.+?}}/).map do |capture|
|
31
|
-
capture.chomp("}}").reverse.chomp("{{").reverse
|
32
|
-
end
|
33
|
-
raise ArgumentError if fields_in_specified_format.any? do |field_name|
|
34
|
-
!note_type.allowed_card_template_question_format_field_names.include?(field_name)
|
35
|
-
end
|
36
|
-
|
37
|
-
@question_format = format
|
38
|
-
end
|
39
|
-
|
40
|
-
##
|
41
|
-
# The answer format of the card template
|
42
|
-
attr_reader :answer_format
|
43
|
-
|
44
|
-
##
|
45
|
-
# Sets the answer format and raises an ArgumentError if the specified format uses invalid fields
|
46
|
-
def answer_format=(format)
|
47
|
-
fields_in_specified_format = format.scan(/{{.+?}}/).map do |capture|
|
48
|
-
capture.chomp("}}").reverse.chomp("{{").reverse
|
49
|
-
end
|
50
|
-
raise ArgumentError if fields_in_specified_format.any? do |field_name|
|
51
|
-
!note_type.allowed_card_template_answer_format_field_names.include?(field_name)
|
52
|
-
end
|
53
|
-
|
54
|
-
@answer_format = format
|
55
|
-
end
|
56
|
-
|
57
|
-
##
|
58
|
-
# The note type that the card template belongs to
|
59
|
-
attr_reader :note_type
|
60
|
-
|
61
|
-
##
|
62
|
-
# 0 for the first card template of the note type, 1 for the second, etc.
|
63
|
-
attr_reader :ordinal_number
|
64
|
-
|
65
|
-
##
|
66
|
-
# Instantiates a new card template called +name+ for the given note type
|
67
|
-
#
|
68
|
-
def initialize(note_type:, name: nil, args: nil)
|
69
|
-
raise ArgumentError unless (name && args.nil?) || (args && args["name"])
|
70
|
-
|
71
|
-
@note_type = note_type
|
72
|
-
if args
|
73
|
-
setup_card_template_instance_variables_from_existing(args: args)
|
74
|
-
else
|
75
|
-
setup_card_template_instance_variables(name: name)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
def setup_card_template_instance_variables_from_existing(args:)
|
82
|
-
@name = args["name"]
|
83
|
-
@ordinal_number = args["ord"]
|
84
|
-
@question_format = args["qfmt"]
|
85
|
-
@answer_format = args["afmt"]
|
86
|
-
@bqfmt = args["bqfmt"]
|
87
|
-
@bafmt = args["bafmt"]
|
88
|
-
@deck_id = args["did"]
|
89
|
-
@browser_font_style = args["bfont"]
|
90
|
-
@browser_font_size = args["bsize"]
|
91
|
-
end
|
92
|
-
|
93
|
-
def setup_card_template_instance_variables(name:)
|
94
|
-
@name = name
|
95
|
-
@ordinal_number = @note_type.card_templates.length
|
96
|
-
@question_format = ""
|
97
|
-
@answer_format = ""
|
98
|
-
@bqfmt = ""
|
99
|
-
@bafmt = ""
|
100
|
-
@deck_id = nil
|
101
|
-
@browser_font_style = ""
|
102
|
-
@browser_font_size = 0
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
@@ -1,105 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "pry"
|
4
|
-
require "json"
|
5
|
-
|
6
|
-
require_relative "deck"
|
7
|
-
require_relative "deck_options_group"
|
8
|
-
require_relative "helpers/time_helper"
|
9
|
-
require_relative "note_type"
|
10
|
-
|
11
|
-
module AnkiRecord
|
12
|
-
##
|
13
|
-
# Collection represents the single record in the Anki database `col` table
|
14
|
-
class Collection
|
15
|
-
include AnkiRecord::TimeHelper
|
16
|
-
|
17
|
-
##
|
18
|
-
# The instance of AnkiRecord::AnkiPackage that this collection object belongs to
|
19
|
-
attr_reader :anki_package
|
20
|
-
|
21
|
-
##
|
22
|
-
# The id attribute will become, or is the same as, the primary key id of this record in the database
|
23
|
-
#
|
24
|
-
# Since there should be only one col record, this attribute should be 1
|
25
|
-
attr_reader :id
|
26
|
-
|
27
|
-
##
|
28
|
-
# The time in milliseconds that the col record was created since the 1970 epoch
|
29
|
-
attr_reader :creation_timestamp
|
30
|
-
|
31
|
-
##
|
32
|
-
# The last time that the col record was modified in milliseconds since the 1970 epoch
|
33
|
-
attr_reader :last_modified_time
|
34
|
-
|
35
|
-
##
|
36
|
-
# An array of the collection's note type objects, which are instances of AnkiRecord::NoteType
|
37
|
-
attr_reader :note_types
|
38
|
-
|
39
|
-
##
|
40
|
-
# An array of the collection's deck objects, which are instances of AnkiRecord::Deck
|
41
|
-
attr_reader :decks
|
42
|
-
|
43
|
-
##
|
44
|
-
# An array of the collection's deck options group objects, which are instances of AnkiRecord::DeckOptionsGroup
|
45
|
-
#
|
46
|
-
# These represent groups of settings that can be applied to a deck.
|
47
|
-
attr_reader :deck_options_groups
|
48
|
-
|
49
|
-
##
|
50
|
-
# Instantiates the collection object for the +anki_package+
|
51
|
-
#
|
52
|
-
# The collection object represents the single record of the collection.anki21 database col table.
|
53
|
-
#
|
54
|
-
# This record stores the note types used by the notes and the decks that they belong to.
|
55
|
-
def initialize(anki_package:)
|
56
|
-
setup_collection_instance_variables(anki_package: anki_package)
|
57
|
-
end
|
58
|
-
|
59
|
-
##
|
60
|
-
# Find one of the collection's note types by name
|
61
|
-
def find_note_type_by(name: nil)
|
62
|
-
note_types.find { |note_type| note_type.name == name }
|
63
|
-
end
|
64
|
-
|
65
|
-
##
|
66
|
-
# Find one of the collection's decks by name
|
67
|
-
def find_deck_by(name: nil)
|
68
|
-
decks.find { |deck| deck.name == name }
|
69
|
-
end
|
70
|
-
|
71
|
-
private
|
72
|
-
|
73
|
-
# rubocop:disable Metrics/MethodLength
|
74
|
-
# rubocop:disable Metrics/AbcSize
|
75
|
-
def setup_collection_instance_variables(anki_package:)
|
76
|
-
@anki_package = anki_package
|
77
|
-
@id = col_record["id"]
|
78
|
-
@creation_timestamp = col_record["crt"]
|
79
|
-
@last_modified_time = col_record["mod"]
|
80
|
-
@scm = col_record["scm"]
|
81
|
-
@ver = col_record["ver"]
|
82
|
-
@dty = col_record["dty"]
|
83
|
-
@usn = col_record["usn"]
|
84
|
-
@ls = col_record["ls"]
|
85
|
-
@configuration = JSON.parse(col_record["conf"])
|
86
|
-
@note_types = JSON.parse(col_record["models"]).values.map do |model_hash|
|
87
|
-
NoteType.new(collection: self, args: model_hash)
|
88
|
-
end
|
89
|
-
@decks = JSON.parse(col_record["decks"]).values.map do |deck_hash|
|
90
|
-
Deck.new(collection: self, args: deck_hash)
|
91
|
-
end
|
92
|
-
@deck_options_groups = JSON.parse(col_record["dconf"]).values.map do |dconf_hash|
|
93
|
-
DeckOptionsGroup.new(collection: self, args: dconf_hash)
|
94
|
-
end
|
95
|
-
@tags = JSON.parse(col_record["tags"])
|
96
|
-
remove_instance_variable(:@col_record)
|
97
|
-
end
|
98
|
-
# rubocop:enable Metrics/AbcSize
|
99
|
-
# rubocop:enable Metrics/MethodLength
|
100
|
-
|
101
|
-
def col_record
|
102
|
-
@col_record ||= @anki_package.execute("select * from col;").first
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
@@ -1,77 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AnkiRecord
|
4
|
-
ANKI_SCHEMA_DEFINITION = <<~SQL # :nodoc:
|
5
|
-
CREATE TABLE col (
|
6
|
-
id integer PRIMARY KEY,
|
7
|
-
crt integer NOT NULL,
|
8
|
-
mod integer NOT NULL,
|
9
|
-
scm integer NOT NULL,
|
10
|
-
ver integer NOT NULL,
|
11
|
-
dty integer NOT NULL,
|
12
|
-
usn integer NOT NULL,
|
13
|
-
ls integer NOT NULL,
|
14
|
-
conf text NOT NULL,
|
15
|
-
models text NOT NULL,
|
16
|
-
decks text NOT NULL,
|
17
|
-
dconf text NOT NULL,
|
18
|
-
tags text NOT NULL
|
19
|
-
);
|
20
|
-
CREATE TABLE notes (
|
21
|
-
id integer PRIMARY KEY,
|
22
|
-
guid text NOT NULL,
|
23
|
-
mid integer NOT NULL,
|
24
|
-
mod integer NOT NULL,
|
25
|
-
usn integer NOT NULL,
|
26
|
-
tags text NOT NULL,
|
27
|
-
flds text NOT NULL,
|
28
|
-
sfld integer NOT NULL,
|
29
|
-
csum integer NOT NULL,
|
30
|
-
flags integer NOT NULL,
|
31
|
-
data text NOT NULL
|
32
|
-
);
|
33
|
-
CREATE TABLE cards (
|
34
|
-
id integer PRIMARY KEY,
|
35
|
-
nid integer NOT NULL,
|
36
|
-
did integer NOT NULL,
|
37
|
-
ord integer NOT NULL,
|
38
|
-
mod integer NOT NULL,
|
39
|
-
usn integer NOT NULL,
|
40
|
-
type integer NOT NULL,
|
41
|
-
queue integer NOT NULL,
|
42
|
-
due integer NOT NULL,
|
43
|
-
ivl integer NOT NULL,
|
44
|
-
factor integer NOT NULL,
|
45
|
-
reps integer NOT NULL,
|
46
|
-
lapses integer NOT NULL,
|
47
|
-
left integer NOT NULL,
|
48
|
-
odue integer NOT NULL,
|
49
|
-
odid integer NOT NULL,
|
50
|
-
flags integer NOT NULL,
|
51
|
-
data text NOT NULL
|
52
|
-
);
|
53
|
-
CREATE TABLE revlog (
|
54
|
-
id integer PRIMARY KEY,
|
55
|
-
cid integer NOT NULL,
|
56
|
-
usn integer NOT NULL,
|
57
|
-
ease integer NOT NULL,
|
58
|
-
ivl integer NOT NULL,
|
59
|
-
lastIvl integer NOT NULL,
|
60
|
-
factor integer NOT NULL,
|
61
|
-
time integer NOT NULL,
|
62
|
-
type integer NOT NULL
|
63
|
-
);
|
64
|
-
CREATE INDEX ix_notes_usn ON notes (usn);
|
65
|
-
CREATE INDEX ix_cards_usn ON cards (usn);
|
66
|
-
CREATE INDEX ix_revlog_usn ON revlog (usn);
|
67
|
-
CREATE INDEX ix_cards_nid ON cards (nid);
|
68
|
-
CREATE INDEX ix_cards_sched ON cards (did, queue, due);
|
69
|
-
CREATE INDEX ix_revlog_cid ON revlog (cid);
|
70
|
-
CREATE INDEX ix_notes_csum ON notes (csum);
|
71
|
-
CREATE TABLE graves (
|
72
|
-
usn integer NOT NULL,
|
73
|
-
oid integer NOT NULL,
|
74
|
-
type integer NOT NULL
|
75
|
-
);
|
76
|
-
SQL
|
77
|
-
end
|
@@ -1,10 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AnkiRecord
|
4
|
-
##
|
5
|
-
# This is the SQL insert statement (as a Ruby string/here document) for the
|
6
|
-
# default col record in the collection.anki21 database exported from a new Anki 2.1.54 profile
|
7
|
-
CLEAN_COLLECTION_21_RECORD = <<~SQL
|
8
|
-
INSERT INTO col VALUES(1,1676883600,0,1676902364657,11,0,0,0,'{"activeDecks":[1],"curDeck":1,"nextPos":1,"schedVer":2,"sortType":"noteFld","estTimes":true,"collapseTime":1200,"dayLearnFirst":false,"curModel":1676902364661,"sortBackwards":false,"newSpread":0,"creationOffset":300,"timeLim":0,"addToCur":true,"dueCounts":true}','{"1676902364664":{"id":1676902364664,"name":"Basic (type in the answer)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}\\n\\n{{type:Back}}","afmt":"{{Front}}\\n\\n<hr id=answer>\\n\\n{{type:Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0,1]]]},"1676902364663":{"id":1676902364663,"name":"Basic (optional reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{#Add Reverse}}{{Back}}{{/Add Reverse}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Add Reverse","ord":2,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"all",[1,2]]]},"1676902364665":{"id":1676902364665,"name":"Cloze","type":1,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Cloze","ord":0,"qfmt":"{{cloze:Text}}","afmt":"{{cloze:Text}}<br>\\n{{Back Extra}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Text","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back Extra","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n.cloze {\\n font-weight: bold;\\n color: blue;\\n}\\n.nightMode .cloze {\\n color: lightblue;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]},"1676902364661":{"id":1676902364661,"name":"Basic","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]},"1676902364662":{"id":1676902364662,"name":"Basic (and reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{Back}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"any",[1]]]}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"lrnToday":[0,0],"revToday":[0,0],"newToday":[0,0],"timeToday":[0,0],"collapsed":true,"browserCollapsed":true,"desc":"","dyn":0,"conf":1,"extendNew":0,"extendRev":0}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"maxTaken":60,"autoplay":true,"timer":0,"replayq":true,"new":{"bury":false,"delays":[1.0,10.0],"initialFactor":2500,"ints":[1,4,0],"order":1,"perDay":20},"rev":{"bury":false,"ease4":1.3,"ivlFct":1.0,"maxIvl":36500,"perDay":200,"hardFactor":1.2},"lapse":{"delays":[10.0],"leechAction":1,"leechFails":8,"minInt":1,"mult":0.0},"dyn":false,"newMix":0,"newPerDayMinimum":0,"interdayLearningMix":0,"reviewOrder":0,"newSortOrder":0,"newGatherPriority":0,"buryInterdayLearning":false}}','{}');
|
9
|
-
SQL
|
10
|
-
end
|
@@ -1,10 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AnkiRecord
|
4
|
-
##
|
5
|
-
# This is the SQL insert statement (as a Ruby string/here document) for the
|
6
|
-
# default col record in the collection.anki2 database exported from a new Anki 2.1.54 profile
|
7
|
-
CLEAN_COLLECTION_2_RECORD = <<~SQL
|
8
|
-
INSERT INTO col VALUES(1,1676883600,1676902390012,1676902390005,11,0,0,0,'{"addToCur":true,"_deck_1_lastNotetype":1676902390008,"nextPos":2,"sortType":"noteFld","newSpread":0,"schedVer":2,"collapseTime":1200,"estTimes":true,"curDeck":1,"creationOffset":300,"dayLearnFirst":false,"timeLim":0,"activeDecks":[1],"sortBackwards":false,"_nt_1676902390008_lastDeck":1,"curModel":1676902390008,"dueCounts":true}','{"1676902390010":{"id":1676902390010,"name":"Basic (optional reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{#Add Reverse}}{{Back}}{{/Add Reverse}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Add Reverse","ord":2,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"all",[1,2]]]},"1676902390009":{"id":1676902390009,"name":"Basic (and reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{Back}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"any",[1]]]},"1676902390008":{"id":1676902390008,"name":"Basic","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]},"1676902390011":{"id":1676902390011,"name":"Basic (type in the answer)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}\\n\\n{{type:Back}}","afmt":"{{Front}}\\n\\n<hr id=answer>\\n\\n{{type:Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0,1]]]},"1676902390012":{"id":1676902390012,"name":"Cloze","type":1,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Cloze","ord":0,"qfmt":"{{cloze:Text}}","afmt":"{{cloze:Text}}<br>\\n{{Back Extra}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Text","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back Extra","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n.cloze {\\n font-weight: bold;\\n color: blue;\\n}\\n.nightMode .cloze {\\n color: lightblue;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"lrnToday":[0,0],"revToday":[0,0],"newToday":[0,0],"timeToday":[0,0],"collapsed":true,"browserCollapsed":true,"desc":"","dyn":0,"conf":1,"extendNew":0,"extendRev":0}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"maxTaken":60,"autoplay":true,"timer":0,"replayq":true,"new":{"bury":false,"delays":[1.0,10.0],"initialFactor":2500,"ints":[1,4,0],"order":1,"perDay":20},"rev":{"bury":false,"ease4":1.3,"ivlFct":1.0,"maxIvl":36500,"perDay":200,"hardFactor":1.2},"lapse":{"delays":[10.0],"leechAction":1,"leechFails":8,"minInt":1,"mult":0.0},"dyn":false,"newMix":0,"newPerDayMinimum":0,"interdayLearningMix":0,"reviewOrder":0,"newSortOrder":0,"newGatherPriority":0,"buryInterdayLearning":false}}','{}');
|
9
|
-
SQL
|
10
|
-
end
|
data/lib/anki_record/deck.rb
DELETED
@@ -1,101 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "pry"
|
4
|
-
|
5
|
-
require_relative "helpers/shared_constants_helper"
|
6
|
-
require_relative "helpers/time_helper"
|
7
|
-
|
8
|
-
module AnkiRecord
|
9
|
-
##
|
10
|
-
# Deck represents an Anki deck
|
11
|
-
class Deck
|
12
|
-
include SharedConstantsHelper
|
13
|
-
include TimeHelper
|
14
|
-
|
15
|
-
DEFAULT_DECK_TODAY_ARRAY = [0, 0].freeze
|
16
|
-
DEFAULT_COLLAPSED = false
|
17
|
-
|
18
|
-
private_constant :DEFAULT_DECK_TODAY_ARRAY, :DEFAULT_COLLAPSED
|
19
|
-
|
20
|
-
##
|
21
|
-
# The collection object that the deck belongs to
|
22
|
-
attr_reader :collection
|
23
|
-
|
24
|
-
##
|
25
|
-
# The name of the deck
|
26
|
-
attr_accessor :name
|
27
|
-
|
28
|
-
##
|
29
|
-
# The description of the deck
|
30
|
-
attr_accessor :description
|
31
|
-
|
32
|
-
##
|
33
|
-
# The id of the deck
|
34
|
-
attr_reader :id
|
35
|
-
|
36
|
-
##
|
37
|
-
# The last time the deck was modified in number of seconds since the epoch
|
38
|
-
#
|
39
|
-
# TODO: is this really supposed to be seconds? Should it be milliseconds?
|
40
|
-
attr_reader :last_modified_time
|
41
|
-
|
42
|
-
##
|
43
|
-
# The id of the eck options/settings group that is applied to the deck
|
44
|
-
attr_reader :deck_options_group_id
|
45
|
-
|
46
|
-
##
|
47
|
-
# Instantiates a new Deck object
|
48
|
-
def initialize(collection:, name: nil, args: nil)
|
49
|
-
raise ArgumentError unless (name && args.nil?) || (args && args["name"])
|
50
|
-
|
51
|
-
@collection = collection
|
52
|
-
if args
|
53
|
-
setup_deck_instance_variables_from_existing(args: args)
|
54
|
-
else
|
55
|
-
setup_deck_instance_variables(name: name)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
# rubocop:disable Metrics/MethodLength
|
62
|
-
# rubocop:disable Metrics/AbcSize
|
63
|
-
def setup_deck_instance_variables_from_existing(args:)
|
64
|
-
@id = args["id"]
|
65
|
-
@last_modified_time = args["mod"]
|
66
|
-
@name = args["name"]
|
67
|
-
@usn = args["usn"]
|
68
|
-
@learn_today = args["lrnToday"]
|
69
|
-
@review_today = args["revToday"]
|
70
|
-
@new_today = args["newToday"]
|
71
|
-
@time_today = args["timeToday"]
|
72
|
-
@collapsed_in_main_window = args["collapsed"]
|
73
|
-
@collapsed_in_browser = args["browserCollapsed"]
|
74
|
-
@description = args["desc"]
|
75
|
-
@dyn = args["dyn"]
|
76
|
-
@deck_options_group_id = args["conf"]
|
77
|
-
@extend_new = args["extendNew"]
|
78
|
-
@extend_review = args["extendRev"]
|
79
|
-
end
|
80
|
-
# rubocop:enable Metrics/MethodLength
|
81
|
-
# rubocop:enable Metrics/AbcSize
|
82
|
-
|
83
|
-
# rubocop:disable Metrics/MethodLength
|
84
|
-
def setup_deck_instance_variables(name:)
|
85
|
-
@id = milliseconds_since_epoch
|
86
|
-
@last_modified_time = seconds_since_epoch
|
87
|
-
@name = name
|
88
|
-
@usn = NEW_OBJECT_USN
|
89
|
-
@learn_today = @review_today = @new_today = @time_today = DEFAULT_DECK_TODAY_ARRAY
|
90
|
-
@collapsed_in_main_window = DEFAULT_COLLAPSED
|
91
|
-
@collapsed_in_browser = DEFAULT_COLLAPSED
|
92
|
-
@description = ""
|
93
|
-
@dyn = NON_FILTERED_DECK_DYN
|
94
|
-
@deck_options_group_id = nil # TODO: Set id to the default deck options group?
|
95
|
-
# TODO: alternatively, if this is nil when the deck is saved, it can be set to the default options group id
|
96
|
-
@extend_new = 0
|
97
|
-
@extend_review = 0
|
98
|
-
end
|
99
|
-
# rubocop:enable Metrics/MethodLength
|
100
|
-
end
|
101
|
-
end
|
data/lib/anki_record/note.rb
DELETED
@@ -1,135 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "pry"
|
4
|
-
require "securerandom"
|
5
|
-
|
6
|
-
require_relative "helpers/checksum_helper"
|
7
|
-
require_relative "helpers/time_helper"
|
8
|
-
|
9
|
-
module AnkiRecord
|
10
|
-
##
|
11
|
-
# Note represents an Anki note
|
12
|
-
class Note
|
13
|
-
include ChecksumHelper
|
14
|
-
include TimeHelper
|
15
|
-
include SharedConstantsHelper
|
16
|
-
|
17
|
-
##
|
18
|
-
# The id of the note
|
19
|
-
attr_reader :id
|
20
|
-
|
21
|
-
##
|
22
|
-
# The globally unique id of the note
|
23
|
-
attr_reader :guid
|
24
|
-
|
25
|
-
##
|
26
|
-
# The last time the note was modified in seconds since the 1970 epoch
|
27
|
-
attr_reader :last_modified_time
|
28
|
-
|
29
|
-
##
|
30
|
-
# The tags applied to the note
|
31
|
-
#
|
32
|
-
# TODO: a setter method for the tags of the note
|
33
|
-
attr_reader :tags
|
34
|
-
|
35
|
-
##
|
36
|
-
# The deck that the note's cards will be put into when saved
|
37
|
-
attr_reader :deck
|
38
|
-
|
39
|
-
##
|
40
|
-
# The note type of the note
|
41
|
-
attr_reader :note_type
|
42
|
-
|
43
|
-
##
|
44
|
-
# The card objects of the note
|
45
|
-
attr_reader :cards
|
46
|
-
|
47
|
-
##
|
48
|
-
# Instantiate a new note for a deck and note type
|
49
|
-
# or TODO: instantiate a new object from an already existing record
|
50
|
-
# rubocop:disable Metrics/MethodLength
|
51
|
-
# rubocop:disable Metrics/AbcSize
|
52
|
-
def initialize(deck:, note_type:)
|
53
|
-
raise ArgumentError unless deck && note_type && deck.collection == note_type.collection
|
54
|
-
|
55
|
-
@apkg = deck.collection.anki_package
|
56
|
-
|
57
|
-
@id = milliseconds_since_epoch
|
58
|
-
@guid = globally_unique_id
|
59
|
-
@last_modified_time = seconds_since_epoch
|
60
|
-
@usn = NEW_OBJECT_USN
|
61
|
-
@tags = []
|
62
|
-
@deck = deck
|
63
|
-
@note_type = note_type
|
64
|
-
@field_contents = setup_field_contents
|
65
|
-
@cards = @note_type.card_templates.map { |card_template| Card.new(note: self, card_template: card_template) }
|
66
|
-
end
|
67
|
-
# rubocop:enable Metrics/MethodLength
|
68
|
-
# rubocop:enable Metrics/AbcSize
|
69
|
-
|
70
|
-
##
|
71
|
-
# Save the note to the collection.anki21 database
|
72
|
-
def save
|
73
|
-
@apkg.execute <<~SQL
|
74
|
-
insert into notes (id, guid, mid, mod, usn, tags, flds, sfld, csum, flags, data)
|
75
|
-
values ('#{@id}', '#{@guid}', '#{note_type.id}', '#{@last_modified_time}', '#{@usn}', '#{@tags.join(" ")}', '#{field_values_separated_by_us}', '#{sort_field_value}', '#{checksum(sort_field_value)}', '0', '')
|
76
|
-
SQL
|
77
|
-
cards.each(&:save)
|
78
|
-
true
|
79
|
-
end
|
80
|
-
|
81
|
-
##
|
82
|
-
# This overrides BasicObject#method_missing and has the effect of creating "ghost methods"
|
83
|
-
#
|
84
|
-
# Specifically, creates setter and getter ghost methods for the fields of the note's note type
|
85
|
-
#
|
86
|
-
# TODO: This should raise a NoMethodError if
|
87
|
-
# the missing method does not end with '=' and is not a field of the note type
|
88
|
-
def method_missing(method_name, field_content = nil)
|
89
|
-
method_name = method_name.to_s
|
90
|
-
return @field_contents[method_name] unless method_name.end_with?("=")
|
91
|
-
|
92
|
-
method_name = method_name.chomp("=")
|
93
|
-
valid_fields_snake_names = @field_contents.keys
|
94
|
-
unless valid_fields_snake_names.include?(method_name)
|
95
|
-
raise ArgumentError, "Valid fields for the #{note_type.name} note type are one of #{valid_fields_snake_names.join(", ")}"
|
96
|
-
end
|
97
|
-
|
98
|
-
@field_contents[method_name] = field_content
|
99
|
-
end
|
100
|
-
|
101
|
-
##
|
102
|
-
# This allows #respond_to? to be accurate for the ghost methods created by #method_missing
|
103
|
-
def respond_to_missing?(method_name, *)
|
104
|
-
method_name = method_name.to_s
|
105
|
-
if method_name.end_with?("=")
|
106
|
-
note_type.snake_case_field_names.include?(method_name.chomp("="))
|
107
|
-
else
|
108
|
-
note_type.snake_case_field_names.include?(method_name)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
private
|
113
|
-
|
114
|
-
def setup_field_contents
|
115
|
-
field_contents = {}
|
116
|
-
note_type.snake_case_field_names.each do |field_name|
|
117
|
-
field_contents[field_name] = ""
|
118
|
-
end
|
119
|
-
field_contents
|
120
|
-
end
|
121
|
-
|
122
|
-
def globally_unique_id
|
123
|
-
SecureRandom.uuid.slice(5...15)
|
124
|
-
end
|
125
|
-
|
126
|
-
def field_values_separated_by_us
|
127
|
-
# The ASCII control code represented by hexadecimal 1F is the Unit Separator (US)
|
128
|
-
note_type.snake_case_field_names.map { |field_name| @field_contents[field_name] }.join("\x1F")
|
129
|
-
end
|
130
|
-
|
131
|
-
def sort_field_value
|
132
|
-
@field_contents[note_type.snake_case_sort_field_name]
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|