anki_record 0.3.0 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +12 -3
- data/CHANGELOG.md +11 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +8 -2
- data/README.md +23 -21
- data/lib/anki_record/anki_package/anki_package.rb +11 -19
- data/lib/anki_record/card/card.rb +2 -2
- data/lib/anki_record/collection/collection.rb +7 -5
- data/lib/anki_record/{anki_package/database_setup_constants.rb → database_setup_constants.rb} +4 -7
- data/lib/anki_record/deck/deck.rb +4 -6
- data/lib/anki_record/deck_options_group/deck_options_group.rb +2 -2
- data/lib/anki_record/helpers/checksum_helper.rb +10 -8
- data/lib/anki_record/helpers/data_query_helper.rb +8 -6
- data/lib/anki_record/helpers/shared_constants_helper.rb +6 -4
- data/lib/anki_record/helpers/time_helper.rb +18 -15
- data/lib/anki_record/note/note.rb +6 -11
- data/lib/anki_record/note/note_attributes.rb +1 -1
- data/lib/anki_record/note/note_guid_helper.rb +10 -0
- data/lib/anki_record/note_type/note_type.rb +4 -4
- data/lib/anki_record/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 909927e79a46b38b4a538e4fd0a32cf9a727ac0a9e276645fdd2296ad6d9a464
|
4
|
+
data.tar.gz: 2e75c487b6345606661d746b7d945b538592bbb6bed16b882a6ce42a87892368
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aeb071caaf37a4fcd72c99f66e31b60676b412890883bf2968400bae46ac756433b0d051495a1ff34e477313ab8a296296e1fc1ecdf2b2227c241439712cef48
|
7
|
+
data.tar.gz: 36e4e21fa7238736ac63239e59507042e6567869add24d09523d201b1cf606738de055484e5c21ea7b37e05a1b5872a76c8241890ad32eaea611f56a8038cbc0
|
data/.rubocop.yml
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-rspec
|
3
|
+
|
1
4
|
AllCops:
|
2
5
|
TargetRubyVersion: 3.2.1
|
3
6
|
NewCops: enable
|
@@ -12,14 +15,15 @@ Style/StringLiteralsInInterpolation:
|
|
12
15
|
|
13
16
|
Layout/LineLength:
|
14
17
|
Max: 120
|
18
|
+
# I like the rspec --format doc output to be very readable.
|
15
19
|
Exclude:
|
16
|
-
- "spec/anki_record
|
20
|
+
- "spec/anki_record/**/*"
|
17
21
|
|
18
22
|
Metrics/BlockLength:
|
19
23
|
Exclude:
|
20
24
|
- "spec/*"
|
21
25
|
- "spec/anki_record/*"
|
22
|
-
- "
|
26
|
+
- "bin/test_scripts/*"
|
23
27
|
|
24
28
|
Layout/IndentationConsistency:
|
25
29
|
EnforcedStyle: indented_internal_methods
|
@@ -28,4 +32,9 @@ Metrics/ClassLength:
|
|
28
32
|
Max: 120
|
29
33
|
|
30
34
|
Style/HashSyntax:
|
31
|
-
EnforcedShorthandSyntax: either
|
35
|
+
EnforcedShorthandSyntax: either
|
36
|
+
|
37
|
+
# One expectation per test is a good practice. For this test suite,
|
38
|
+
# following this rule would have a very high performance cost.
|
39
|
+
RSpec/MultipleExpectations:
|
40
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -35,4 +35,14 @@
|
|
35
35
|
- In fixing this bug, other issues with `tags` and `vers` were introduced and then fixed.
|
36
36
|
- It was also noticed that the default note types with "Basic" in the name should not have `tags` and `vers` so this was changed too.
|
37
37
|
- API documentation changed from using RDoc to SDoc with the Rails template.
|
38
|
-
- RSpec test suite was refactored to improve speed: 4 minutes -> 1.5 minutes.
|
38
|
+
- RSpec test suite was refactored to improve speed: 4 minutes -> 1.5 minutes.
|
39
|
+
|
40
|
+
## [0.3.1] - 04-29-2023
|
41
|
+
|
42
|
+
- `Deck.new` was saving the deck to the `collection.anki21` database. Now it will only instantiate it and `#save` must be called to save it.
|
43
|
+
- `Helper` modules moved into the `Helpers` module namespace.
|
44
|
+
- Bug fix addressing using the approximate milliseconds since the epoch as the primary key id causing the uniqueness constraint to fail when creating a lot of notes.
|
45
|
+
|
46
|
+
## [0.3.2] - 05-20-2023
|
47
|
+
- The private `Note` method `globally_unique_id` has been moved to `NoteGuidHelper` and included into note.
|
48
|
+
- The `guid` attribute also now has a public setter.
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
anki_record (0.3.
|
4
|
+
anki_record (0.3.2)
|
5
5
|
rubyzip (>= 2.3)
|
6
6
|
sqlite3 (~> 1.3)
|
7
7
|
|
@@ -53,6 +53,11 @@ GEM
|
|
53
53
|
unicode-display_width (>= 2.4.0, < 3.0)
|
54
54
|
rubocop-ast (1.24.1)
|
55
55
|
parser (>= 3.1.1.0)
|
56
|
+
rubocop-capybara (2.17.1)
|
57
|
+
rubocop (~> 1.41)
|
58
|
+
rubocop-rspec (2.20.0)
|
59
|
+
rubocop (~> 1.33)
|
60
|
+
rubocop-capybara (~> 2.17)
|
56
61
|
ruby-progressbar (1.11.0)
|
57
62
|
rubyzip (2.3.2)
|
58
63
|
sdoc (2.6.1)
|
@@ -63,7 +68,7 @@ GEM
|
|
63
68
|
simplecov_json_formatter (~> 0.1)
|
64
69
|
simplecov-html (0.12.3)
|
65
70
|
simplecov_json_formatter (0.1.4)
|
66
|
-
sqlite3 (1.6.
|
71
|
+
sqlite3 (1.6.2-x86_64-linux)
|
67
72
|
stringio (3.0.5)
|
68
73
|
unicode-display_width (2.4.2)
|
69
74
|
|
@@ -76,6 +81,7 @@ DEPENDENCIES
|
|
76
81
|
rake (~> 13.0)
|
77
82
|
rspec (~> 3.0)
|
78
83
|
rubocop (~> 1.21)
|
84
|
+
rubocop-rspec (~> 2.20)
|
79
85
|
sdoc (~> 2.6)
|
80
86
|
simplecov
|
81
87
|
|
data/README.md
CHANGED
@@ -1,10 +1,6 @@
|
|
1
1
|
# Anki Record
|
2
2
|
|
3
|
-
Anki Record is a Ruby
|
4
|
-
|
5
|
-
**Development is ongoing and currently released versions may have some bugs. The library does not support media yet.**
|
6
|
-
|
7
|
-
**To import the deck packages into Anki, click the File menu and then "Import" from inside Anki. Do not double click the `.apkg` file to open Anki and import it at the same time.**
|
3
|
+
Anki Record is a Ruby gem providing an API to Anki flashcard deck packages (zipped SQLite databases). The main thing it does not support yet is adding media to the notes.
|
8
4
|
|
9
5
|
## Installation
|
10
6
|
|
@@ -18,12 +14,12 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
18
14
|
|
19
15
|
## Usage
|
20
16
|
|
21
|
-
The Anki package object is instantiated with `AnkiRecord::AnkiPackage.new`. If this is passed a block,
|
17
|
+
The Anki package object is instantiated with `AnkiRecord::AnkiPackage.new`. If this is passed a block, the collection object is yielded to the block, and an Anki deck package file is created after execution of the block:
|
22
18
|
|
23
19
|
```ruby
|
24
20
|
require "anki_record"
|
25
21
|
|
26
|
-
AnkiRecord::AnkiPackage.new(name: "test") do |
|
22
|
+
AnkiRecord::AnkiPackage.new(name: "test") do |collection|
|
27
23
|
3.times do |number|
|
28
24
|
puts "#{3 - number}..."
|
29
25
|
end
|
@@ -42,27 +38,29 @@ Alternatively, if `AnkiRecord::Package::new` is not passed a block, the `zip` me
|
|
42
38
|
require "anki_record"
|
43
39
|
|
44
40
|
apkg = AnkiRecord::AnkiPackage.new(name: "test")
|
41
|
+
collection = apkg.collection
|
42
|
+
# Add notes to the collection
|
45
43
|
apkg.zip # This zips the temporary files into test.apkg, and then deletes them.
|
46
44
|
```
|
47
45
|
|
48
|
-
The second, optional argument to `AnkiRecord::
|
46
|
+
The second, optional argument to `AnkiRecord::AnkiPackage.new` is `target_directory`. The default value is the current working directory, but if a relative file path argument is given, the new `*.apkg` file will be saved in that directory. An exception will be raised if the relative file path is not to a directory that exists.
|
49
47
|
|
50
48
|
A new Anki package object is initialized with the "Default" deck and the default note types of a new Anki collection (including "Basic" and "Cloze"). The deck and note type objects are accessed through the `collection` attribute of the Anki package object through the `find_deck_by` and `find_note_type_by` methods passed the `name` keyword argument:
|
51
49
|
|
52
50
|
```ruby
|
53
51
|
require "anki_record"
|
54
52
|
|
55
|
-
|
56
|
-
deck =
|
53
|
+
AnkiRecord::AnkiPackage.new(name: "test") do |collection|
|
54
|
+
deck = collection.find_deck_by name: "Default"
|
57
55
|
|
58
|
-
note_type =
|
56
|
+
note_type = collection.find_note_type_by name: "Basic"
|
59
57
|
|
60
58
|
note = AnkiRecord::Note.new note_type: note_type, deck: deck
|
61
59
|
note.front = "Hello"
|
62
60
|
note.back = "World"
|
63
61
|
note.save
|
64
62
|
|
65
|
-
note_type2 =
|
63
|
+
note_type2 = collection.find_note_type_by name: "Cloze"
|
66
64
|
|
67
65
|
note2 = AnkiRecord::Note.new note_type: note_type2, deck: deck
|
68
66
|
note2.text = "Cloze {{c1::Hello}}"
|
@@ -83,6 +81,7 @@ note_id = nil
|
|
83
81
|
|
84
82
|
AnkiRecord::AnkiPackage.new(name: "test_1") do |collection|
|
85
83
|
crazy_deck = AnkiRecord::Deck.new collection: collection, name: "test_1_deck"
|
84
|
+
crazy_deck.save
|
86
85
|
|
87
86
|
crazy_note_type = AnkiRecord::NoteType.new collection: collection, name: "test 1 note type"
|
88
87
|
AnkiRecord::NoteField.new note_type: crazy_note_type, name: "crazy front"
|
@@ -127,19 +126,22 @@ This script creates an Anki package `test_1.apkg` with a new deck and new note t
|
|
127
126
|
|
128
127
|
The [API Documentation](https://kylerego.github.io/anki_record_docs) is generated using SDoc from comments in the source code. You might notice that some public methods are intentionally omitted from this documentation. Although public, these methods are not intended to be used outside of the gem's implementation and should be treated as private.
|
129
128
|
|
130
|
-
The RSpec examples are intended to provide executable documentation and may also be helpful to understand the API. Running the test suite with the `rspec` command will output these in a more readable way that also reflects the nesting of the RSpec examples and example groups.
|
129
|
+
The RSpec examples are intended to provide executable documentation and may also be helpful to understand the API. Running the test suite with the `rspec` command will output these in a more readable way that also reflects the nesting of the RSpec examples and example groups. This is an example of part of the output:
|
131
130
|
|
132
131
|
```
|
133
|
-
AnkiRecord::
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
132
|
+
AnkiRecord::Deck#save
|
133
|
+
when the deck does not exist in the collection.anki21 database
|
134
|
+
saves the deck object's id as a key in the decks column's JSON object in the collection.anki21 database
|
135
|
+
saves the deck object as a hash, as the value of the deck object's id key, in the decks hash
|
136
|
+
saves the deck object as a hash with the following keys: 'id', 'mod', 'name', 'usn', 'lrnToday', 'revToday', 'newToday', 'timeToday', 'collapsed', 'browserCollapsed', 'desc', 'dyn', 'conf', 'extendNew', 'extendRev'
|
137
|
+
saves the deck object as a hash with the deck object's id attribute as the value for the id key in the deck hash
|
138
|
+
saves the deck object as a hash with the deck object's last_modified_timestamp attribute as the value for the mod key in the deck hash
|
139
|
+
|
138
140
|
```
|
139
141
|
|
140
|
-
The RSpec test suite files in `spec`
|
142
|
+
The RSpec test suite files in `spec` are organized similarly to the the source code in `lib`.
|
141
143
|
|
142
|
-
## Development
|
144
|
+
<!-- ## Development
|
143
145
|
|
144
146
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
145
147
|
|
@@ -174,7 +176,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
174
176
|
- Update usage examples
|
175
177
|
- Update and regenerate documentation
|
176
178
|
- Bump version
|
177
|
-
- Release gem
|
179
|
+
- Release gem -->
|
178
180
|
|
179
181
|
<!-- ## Contributing
|
180
182
|
|
@@ -5,16 +5,14 @@ require "pathname"
|
|
5
5
|
require_relative "../card/card"
|
6
6
|
require_relative "../collection/collection"
|
7
7
|
require_relative "../note/note"
|
8
|
-
require_relative "database_setup_constants"
|
8
|
+
require_relative "../database_setup_constants"
|
9
9
|
|
10
10
|
# rubocop:disable Metrics/ClassLength
|
11
11
|
module AnkiRecord
|
12
12
|
##
|
13
|
-
# AnkiPackage represents an Anki package.
|
14
|
-
#
|
15
|
-
# Here, Anki package refers to the zip file that Anki can export and import.
|
13
|
+
# AnkiPackage represents an Anki package deck file.
|
16
14
|
class AnkiPackage
|
17
|
-
include AnkiRecord::DataQueryHelper
|
15
|
+
include AnkiRecord::Helpers::DataQueryHelper
|
18
16
|
|
19
17
|
##
|
20
18
|
# The package's collection object.
|
@@ -45,8 +43,8 @@ module AnkiRecord
|
|
45
43
|
|
46
44
|
private
|
47
45
|
|
48
|
-
def execute_closure_and_zip(
|
49
|
-
closure.call(
|
46
|
+
def execute_closure_and_zip(collection, &closure)
|
47
|
+
closure.call(collection)
|
50
48
|
rescue StandardError => e
|
51
49
|
destroy_temporary_directory
|
52
50
|
puts_error_and_standard_message(error: e)
|
@@ -151,15 +149,7 @@ module AnkiRecord
|
|
151
149
|
end
|
152
150
|
|
153
151
|
# rubocop:disable Metrics/MethodLength
|
154
|
-
|
155
|
-
##
|
156
|
-
# Unzips the *.apkg file that was opened and yields its collection.anki21 database
|
157
|
-
# as a SQLite3::Database object (see sqlite3 gem) to the block.
|
158
|
-
#
|
159
|
-
# After the block executes, the files created by unzipping are deleted.
|
160
|
-
#
|
161
|
-
# Throws an error if the Anki package was not instantiated using ::open.
|
162
|
-
#
|
152
|
+
# :nodoc:
|
163
153
|
def temporarily_unzip_source_apkg
|
164
154
|
raise ArgumentError unless @open_path && block_given?
|
165
155
|
|
@@ -179,7 +169,7 @@ module AnkiRecord
|
|
179
169
|
# rubocop:enable Metrics/MethodLength
|
180
170
|
|
181
171
|
class << self
|
182
|
-
include TimeHelper
|
172
|
+
include Helpers::TimeHelper
|
183
173
|
|
184
174
|
# rubocop:disable Metrics/MethodLength
|
185
175
|
# rubocop:disable Metrics/AbcSize
|
@@ -233,11 +223,13 @@ module AnkiRecord
|
|
233
223
|
|
234
224
|
public
|
235
225
|
|
236
|
-
|
226
|
+
# :nodoc:
|
227
|
+
def open?
|
237
228
|
!closed?
|
238
229
|
end
|
239
230
|
|
240
|
-
|
231
|
+
# :nodoc:
|
232
|
+
def closed?
|
241
233
|
@anki21_database.closed?
|
242
234
|
end
|
243
235
|
end
|
@@ -9,8 +9,8 @@ module AnkiRecord
|
|
9
9
|
# Card represents an Anki card.
|
10
10
|
class Card
|
11
11
|
include CardAttributes
|
12
|
-
include TimeHelper
|
13
|
-
include SharedConstantsHelper
|
12
|
+
include Helpers::TimeHelper
|
13
|
+
include Helpers::SharedConstantsHelper
|
14
14
|
|
15
15
|
def initialize(note:, card_template: nil, card_data: nil) # :nodoc:
|
16
16
|
@note = note
|
@@ -14,9 +14,9 @@ module AnkiRecord
|
|
14
14
|
# Collection represents the single record in the Anki collection.anki21 database's `col` table.
|
15
15
|
# The note types, decks, and deck options groups data are contained within this record.
|
16
16
|
class Collection
|
17
|
-
include
|
18
|
-
include
|
19
|
-
include
|
17
|
+
include Helpers::DataQueryHelper
|
18
|
+
include Helpers::TimeHelper
|
19
|
+
include CollectionAttributes
|
20
20
|
|
21
21
|
def initialize(anki_package:) # :nodoc:
|
22
22
|
setup_collection_instance_variables(anki_package: anki_package)
|
@@ -107,11 +107,13 @@ module AnkiRecord
|
|
107
107
|
AnkiRecord::Note.new collection: self, data: note_cards_data
|
108
108
|
end
|
109
109
|
|
110
|
-
|
110
|
+
# :nodoc:
|
111
|
+
def decks_json
|
111
112
|
JSON.parse(anki_package.prepare("select decks from col;").execute.first["decks"])
|
112
113
|
end
|
113
114
|
|
114
|
-
|
115
|
+
# :nodoc:
|
116
|
+
def models_json
|
115
117
|
JSON.parse(anki_package.prepare("select models from col;").execute.first["models"])
|
116
118
|
end
|
117
119
|
|
data/lib/anki_record/{anki_package/database_setup_constants.rb → database_setup_constants.rb}
RENAMED
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AnkiRecord
|
4
|
-
|
4
|
+
# :nodoc:
|
5
|
+
ANKI_SCHEMA_DEFINITION = <<~SQL
|
5
6
|
CREATE TABLE col (
|
6
7
|
id integer PRIMARY KEY,
|
7
8
|
crt integer NOT NULL,
|
@@ -75,16 +76,12 @@ module AnkiRecord
|
|
75
76
|
);
|
76
77
|
SQL
|
77
78
|
|
78
|
-
|
79
|
-
# This is the SQL insert statement (as a Ruby string/here document) for the
|
80
|
-
# default col record in the collection.anki2 database exported from a new Anki 2.1.54 profile
|
79
|
+
# :nodoc:
|
81
80
|
INSERT_COLLECTION_ANKI_2_COL_RECORD = <<~SQL
|
82
81
|
INSERT INTO col VALUES(1,1676883600,1676902390012,1676902390005,11,0,0,0,'{"addToCur":true,"_deck_1_lastNotetype":1676902390008,"nextPos":2,"sortType":"noteFld","newSpread":0,"schedVer":2,"collapseTime":1200,"estTimes":true,"curDeck":1,"creationOffset":300,"dayLearnFirst":false,"timeLim":0,"activeDecks":[1],"sortBackwards":false,"_nt_1676902390008_lastDeck":1,"curModel":1676902390008,"dueCounts":true}','{"1676902390010":{"id":1676902390010,"name":"Basic (optional reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{#Add Reverse}}{{Back}}{{/Add Reverse}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Add Reverse","ord":2,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"all",[1,2]]]},"1676902390009":{"id":1676902390009,"name":"Basic (and reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{Back}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"any",[1]]]},"1676902390008":{"id":1676902390008,"name":"Basic","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]},"1676902390011":{"id":1676902390011,"name":"Basic (type in the answer)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}\\n\\n{{type:Back}}","afmt":"{{Front}}\\n\\n<hr id=answer>\\n\\n{{type:Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0,1]]]},"1676902390012":{"id":1676902390012,"name":"Cloze","type":1,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Cloze","ord":0,"qfmt":"{{cloze:Text}}","afmt":"{{cloze:Text}}<br>\\n{{Back Extra}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Text","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back Extra","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n.cloze {\\n font-weight: bold;\\n color: blue;\\n}\\n.nightMode .cloze {\\n color: lightblue;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"lrnToday":[0,0],"revToday":[0,0],"newToday":[0,0],"timeToday":[0,0],"collapsed":true,"browserCollapsed":true,"desc":"","dyn":0,"conf":1,"extendNew":0,"extendRev":0}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"maxTaken":60,"autoplay":true,"timer":0,"replayq":true,"new":{"bury":false,"delays":[1.0,10.0],"initialFactor":2500,"ints":[1,4,0],"order":1,"perDay":20},"rev":{"bury":false,"ease4":1.3,"ivlFct":1.0,"maxIvl":36500,"perDay":200,"hardFactor":1.2},"lapse":{"delays":[10.0],"leechAction":1,"leechFails":8,"minInt":1,"mult":0.0},"dyn":false,"newMix":0,"newPerDayMinimum":0,"interdayLearningMix":0,"reviewOrder":0,"newSortOrder":0,"newGatherPriority":0,"buryInterdayLearning":false}}','{}');
|
83
82
|
SQL
|
84
83
|
|
85
|
-
|
86
|
-
# This is the SQL insert statement (as a Ruby string/here document) for the
|
87
|
-
# default col record in the collection.anki21 database exported from a new Anki 2.1.54 profile
|
84
|
+
# :nodoc:
|
88
85
|
INSERT_COLLECTION_ANKI_21_COL_RECORD = <<~SQL
|
89
86
|
INSERT INTO col VALUES(1,1676883600,0,1676902364657,11,0,0,0,'{"activeDecks":[1],"curDeck":1,"nextPos":1,"schedVer":2,"sortType":"noteFld","estTimes":true,"collapseTime":1200,"dayLearnFirst":false,"curModel":1676902364661,"sortBackwards":false,"newSpread":0,"creationOffset":300,"timeLim":0,"addToCur":true,"dueCounts":true}','{"1676902364664":{"id":1676902364664,"name":"Basic (type in the answer)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}\\n\\n{{type:Back}}","afmt":"{{Front}}\\n\\n<hr id=answer>\\n\\n{{type:Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0,1]]]},"1676902364663":{"id":1676902364663,"name":"Basic (optional reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{#Add Reverse}}{{Back}}{{/Add Reverse}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Add Reverse","ord":2,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"all",[1,2]]]},"1676902364665":{"id":1676902364665,"name":"Cloze","type":1,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Cloze","ord":0,"qfmt":"{{cloze:Text}}","afmt":"{{cloze:Text}}<br>\\n{{Back Extra}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Text","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back Extra","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n.cloze {\\n font-weight: bold;\\n color: blue;\\n}\\n.nightMode .cloze {\\n color: lightblue;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]},"1676902364661":{"id":1676902364661,"name":"Basic","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]},"1676902364662":{"id":1676902364662,"name":"Basic (and reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{Back}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"any",[1]]]}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"lrnToday":[0,0],"revToday":[0,0],"newToday":[0,0],"timeToday":[0,0],"collapsed":true,"browserCollapsed":true,"desc":"","dyn":0,"conf":1,"extendNew":0,"extendRev":0}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"maxTaken":60,"autoplay":true,"timer":0,"replayq":true,"new":{"bury":false,"delays":[1.0,10.0],"initialFactor":2500,"ints":[1,4,0],"order":1,"perDay":20},"rev":{"bury":false,"ease4":1.3,"ivlFct":1.0,"maxIvl":36500,"perDay":200,"hardFactor":1.2},"lapse":{"delays":[10.0],"leechAction":1,"leechFails":8,"minInt":1,"mult":0.0},"dyn":false,"newMix":0,"newPerDayMinimum":0,"interdayLearningMix":0,"reviewOrder":0,"newSortOrder":0,"newGatherPriority":0,"buryInterdayLearning":false}}','{}');
|
90
87
|
SQL
|
@@ -8,13 +8,11 @@ require_relative "../helpers/time_helper"
|
|
8
8
|
module AnkiRecord
|
9
9
|
##
|
10
10
|
# Deck represents an Anki deck.
|
11
|
-
# In the collection.anki21 database, the deck is a JSON object
|
12
|
-
# which is part of a larger JSON object: the value of the col record's decks column.
|
13
11
|
class Deck
|
14
12
|
include DeckAttributes
|
15
13
|
include DeckDefaults
|
16
|
-
include SharedConstantsHelper
|
17
|
-
include TimeHelper
|
14
|
+
include Helpers::SharedConstantsHelper
|
15
|
+
include Helpers::TimeHelper
|
18
16
|
|
19
17
|
##
|
20
18
|
# Instantiates a new Deck object belonging to +collection+ with name +name+.
|
@@ -29,11 +27,11 @@ module AnkiRecord
|
|
29
27
|
end
|
30
28
|
|
31
29
|
@collection.add_deck self
|
32
|
-
save
|
30
|
+
save if args
|
33
31
|
end
|
34
32
|
|
35
33
|
##
|
36
|
-
# Saves the deck
|
34
|
+
# Saves the deck to the collection.anki21 database.
|
37
35
|
def save
|
38
36
|
collection_decks_hash = collection.decks_json
|
39
37
|
collection_decks_hash[@id] = to_h
|
@@ -9,8 +9,8 @@ module AnkiRecord
|
|
9
9
|
# DeckOptionsGroup represents a set of options that can be applied to an Anki deck.
|
10
10
|
class DeckOptionsGroup
|
11
11
|
include DeckOptionsGroupAttributes
|
12
|
-
include SharedConstantsHelper
|
13
|
-
include TimeHelper
|
12
|
+
include Helpers::SharedConstantsHelper
|
13
|
+
include Helpers::TimeHelper
|
14
14
|
|
15
15
|
##
|
16
16
|
# Instantiates a new deck options group belonging to +collection+ with name +name+.
|
@@ -3,15 +3,17 @@
|
|
3
3
|
require "digest"
|
4
4
|
|
5
5
|
module AnkiRecord
|
6
|
-
|
7
|
-
# A module for the method that calculates the checksum value of notes.
|
8
|
-
#
|
9
|
-
# This checksum may be used by Anki to detect duplicates.
|
10
|
-
module ChecksumHelper
|
6
|
+
module Helpers
|
11
7
|
##
|
12
|
-
#
|
13
|
-
|
14
|
-
|
8
|
+
# A module for the method that calculates the checksum value of notes.
|
9
|
+
#
|
10
|
+
# This checksum may be used by Anki to detect duplicates.
|
11
|
+
module ChecksumHelper
|
12
|
+
##
|
13
|
+
# Returns the integer representation of the first 8 characters of the SHA-1 digest of the +sfld+ argument
|
14
|
+
def checksum(sfld)
|
15
|
+
Digest::SHA1.hexdigest(sfld)[0...8].to_i(16).to_s
|
16
|
+
end
|
15
17
|
end
|
16
18
|
end
|
17
19
|
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AnkiRecord
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module Helpers
|
5
|
+
module DataQueryHelper # :nodoc:
|
6
|
+
def note_cards_data_for_note_id(sql_able:, id:)
|
7
|
+
note_data = sql_able.prepare("select * from notes where id = ?").execute([id]).first
|
8
|
+
return nil unless note_data
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
cards_data = sql_able.prepare("select * from cards where nid = ?").execute([id]).to_a
|
11
|
+
{ note_data: note_data, cards_data: cards_data }
|
12
|
+
end
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AnkiRecord
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module Helpers
|
5
|
+
module SharedConstantsHelper # :nodoc:
|
6
|
+
NEW_OBJECT_USN = -1
|
7
|
+
NON_FILTERED_DECK_DYN = 0
|
8
|
+
private_constant :NEW_OBJECT_USN, :NON_FILTERED_DECK_DYN
|
9
|
+
end
|
8
10
|
end
|
9
11
|
end
|
@@ -3,23 +3,26 @@
|
|
3
3
|
require "date"
|
4
4
|
|
5
5
|
module AnkiRecord
|
6
|
-
|
7
|
-
# Helper module to calculate integer time values since the 1970 epoch.
|
8
|
-
#
|
9
|
-
# Specifically, the time that has passed since 00:00:00 UTC Jan 1 1970.
|
10
|
-
module TimeHelper
|
6
|
+
module Helpers
|
11
7
|
##
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
-
|
8
|
+
# Helper module to calculate integer time values since the 1970 epoch.
|
9
|
+
#
|
10
|
+
# Specifically, the time that has passed since 00:00:00 UTC Jan 1 1970.
|
11
|
+
module TimeHelper
|
12
|
+
##
|
13
|
+
# Returns approximately the number of milliseconds since the 1970 epoch.
|
14
|
+
# This is used for some of the primary key ids. To prevent violation of the
|
15
|
+
# uniqueness constraint, sleep is called for 1 millisecond.
|
16
|
+
def milliseconds_since_epoch
|
17
|
+
sleep 0.001
|
18
|
+
DateTime.now.strftime("%Q").to_i
|
19
|
+
end
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
##
|
22
|
+
# Returns approximately the number of seconds since the 1970 epoch.
|
23
|
+
def seconds_since_epoch
|
24
|
+
Time.now.to_i
|
25
|
+
end
|
23
26
|
end
|
24
27
|
end
|
25
28
|
end
|
@@ -5,22 +5,21 @@ require "securerandom"
|
|
5
5
|
require_relative "../helpers/checksum_helper"
|
6
6
|
require_relative "../helpers/time_helper"
|
7
7
|
require_relative "note_attributes"
|
8
|
+
require_relative "note_guid_helper"
|
8
9
|
|
9
10
|
# rubocop:disable Metrics/ClassLength
|
10
11
|
module AnkiRecord
|
11
12
|
##
|
12
|
-
# Represents an Anki note.
|
13
|
-
# table in the collection.anki21 database.
|
13
|
+
# Represents an Anki note.
|
14
14
|
class Note
|
15
|
-
include ChecksumHelper
|
15
|
+
include Helpers::ChecksumHelper
|
16
16
|
include NoteAttributes
|
17
|
-
include
|
18
|
-
include
|
17
|
+
include NoteGuidHelper
|
18
|
+
include Helpers::TimeHelper
|
19
|
+
include Helpers::SharedConstantsHelper
|
19
20
|
|
20
21
|
##
|
21
22
|
# Instantiates a note of type +note_type+ and belonging to deck +deck+.
|
22
|
-
#
|
23
|
-
# If +note_type+ and +deck+ arguments are used, +collection+ and +data should not be given.
|
24
23
|
def initialize(note_type: nil, deck: nil, collection: nil, data: nil)
|
25
24
|
if note_type && deck
|
26
25
|
setup_instance_variables_for_new_note(note_type: note_type, deck: deck)
|
@@ -164,10 +163,6 @@ module AnkiRecord
|
|
164
163
|
|
165
164
|
private
|
166
165
|
|
167
|
-
def globally_unique_id
|
168
|
-
SecureRandom.uuid.slice(5...15)
|
169
|
-
end
|
170
|
-
|
171
166
|
def field_values_separated_by_us
|
172
167
|
# The ASCII control code represented by hexadecimal 1F is the Unit Separator (US)
|
173
168
|
note_type.snake_case_field_names.map { |field_name| @field_contents[field_name] }.join("\x1F")
|
@@ -13,10 +13,10 @@ module AnkiRecord
|
|
13
13
|
#
|
14
14
|
# The attributes are documented in the NoteTypeAttributes module.
|
15
15
|
class NoteType
|
16
|
-
include
|
17
|
-
include
|
18
|
-
include
|
19
|
-
include
|
16
|
+
include Helpers::SharedConstantsHelper
|
17
|
+
include Helpers::TimeHelper
|
18
|
+
include NoteTypeAttributes
|
19
|
+
include NoteTypeDefaults
|
20
20
|
|
21
21
|
NOTE_TYPES_WITHOUT_TAGS_AND_VERS_VALUES = ["Basic", "Basic (and reversed card)",
|
22
22
|
"Basic (optional reversed card)", "Basic (type in the answer)"].freeze
|
data/lib/anki_record/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: anki_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kyle Rego
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubyzip
|
@@ -58,13 +58,13 @@ files:
|
|
58
58
|
- anki_record.gemspec
|
59
59
|
- lib/anki_record.rb
|
60
60
|
- lib/anki_record/anki_package/anki_package.rb
|
61
|
-
- lib/anki_record/anki_package/database_setup_constants.rb
|
62
61
|
- lib/anki_record/card/card.rb
|
63
62
|
- lib/anki_record/card/card_attributes.rb
|
64
63
|
- lib/anki_record/card_template/card_template.rb
|
65
64
|
- lib/anki_record/card_template/card_template_attributes.rb
|
66
65
|
- lib/anki_record/collection/collection.rb
|
67
66
|
- lib/anki_record/collection/collection_attributes.rb
|
67
|
+
- lib/anki_record/database_setup_constants.rb
|
68
68
|
- lib/anki_record/deck/deck.rb
|
69
69
|
- lib/anki_record/deck/deck_attributes.rb
|
70
70
|
- lib/anki_record/deck/deck_defaults.rb
|
@@ -76,6 +76,7 @@ files:
|
|
76
76
|
- lib/anki_record/helpers/time_helper.rb
|
77
77
|
- lib/anki_record/note/note.rb
|
78
78
|
- lib/anki_record/note/note_attributes.rb
|
79
|
+
- lib/anki_record/note/note_guid_helper.rb
|
79
80
|
- lib/anki_record/note_field/note_field.rb
|
80
81
|
- lib/anki_record/note_field/note_field_attributes.rb
|
81
82
|
- lib/anki_record/note_field/note_field_defaults.rb
|