anki_record 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4da2246d01a22eec129d18c242062ac1eb39b9b794129bf4666fc7eb2615087b
4
- data.tar.gz: 5de4bdb4e80e078d8b5711466b2eba97b68563bfbb44f24805fc63b909a31d08
3
+ metadata.gz: b89baf921abc0de4f35babe65cb40cf5fa5647d4d1fe9b3b81ecb80f8a47a15b
4
+ data.tar.gz: f766e3344683a73d46450c8bf3cf6ae8226f4cbefee4d5126a194c6dcd831cab
5
5
  SHA512:
6
- metadata.gz: ac4bb975b12ca50c5d9bba91caeec3accd75a952f04ae88a17ebef9b6b15cc7af335316ee024f1f50322ec77d321aad9106c6fac3a99eb657f48a1f513353c35
7
- data.tar.gz: 62b7cbd1d173f23eb2c1a3b816a092f7cbd7200945ce8bea5aafa2d73dc59714f571f2869aadb62016749dbc2446d0f43cb16fc5d670f89ea8bc3507e2c2a2f0
6
+ metadata.gz: 3533d5803a630df54a2c52605fefc8f4dc4ce87e0a649a34672a7a3e369a53722f7d2481e9362388519eb89bd59fbfce8f7f226e0e489433e1d1a7d12fb285fd
7
+ data.tar.gz: 7b2ee79fd7ba76b1f229e06831909d3e4b7949c6edc79e608fdc2aae6fe481caa9ccf14c0cd586212b78ef1deddf9e47a91d505c30bd645cf4dae8282e3ff6a8
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
- - "spec/integration/*"
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,10 @@
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.4.0] - Not released
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.
data/Gemfile CHANGED
@@ -18,3 +18,5 @@ gem "rubocop", "~> 1.21"
18
18
  gem "pry"
19
19
 
20
20
  gem "sdoc", "~> 2.6"
21
+
22
+ gem "rubocop-rspec", "~> 2.20"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- anki_record (0.3.0)
4
+ anki_record (0.3.1)
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)
@@ -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 library which provides a programmatic interface to Anki flashcard decks/deck packages (`.apkg` files or Anki SQLite databases).
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, it will execute the block, and afterwards zip an `*.apkg` file where `*` is the name argument (this argument is not allowed to contain spaces):
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 |apkg|
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::Package.new` is `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.
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
- apkg = AnkiRecord::AnkiPackage.new(name: "test") do |apkg|
56
- deck = apkg.collection.find_deck_by name: "Default"
53
+ AnkiRecord::AnkiPackage.new(name: "test") do |collection|
54
+ deck = collection.find_deck_by name: "Default"
57
55
 
58
- note_type = apkg.collection.find_note_type_by name: "Basic"
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 = apkg.collection.find_note_type_by name: "Cloze"
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. The following is an example of output from one example in `spec/anki_record/note_spec.rb`:
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::Note
134
- #save
135
- for a note that does not exist in the collection.anki21 database (custom note type, 2 card templates)
136
- should save two card records to the collection.anki21 database
137
- with nid values equal to the id of the cards' note object's id
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` have a mapping with the source code in `lib`.
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(object_to_yield, &closure)
49
- closure.call(object_to_yield)
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
- def open? # :nodoc:
226
+ # :nodoc:
227
+ def open?
237
228
  !closed?
238
229
  end
239
230
 
240
- def closed? # :nodoc:
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 AnkiRecord::DataQueryHelper
18
- include AnkiRecord::TimeHelper
19
- include AnkiRecord::CollectionAttributes
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
- def decks_json # :nodoc:
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
- def models_json # :nodoc:
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
 
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AnkiRecord
4
- ANKI_SCHEMA_DEFINITION = <<~SQL # :nodoc:
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 (or updates it) in the collection.anki21 database.
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
- # Returns the integer representation of the first 8 characters of the SHA-1 digest of the +sfld+ argument
13
- def checksum(sfld)
14
- Digest::SHA1.hexdigest(sfld)[0...8].to_i(16).to_s
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 DataQueryHelper # :nodoc:
5
- def note_cards_data_for_note_id(sql_able:, id:)
6
- note_data = sql_able.prepare("select * from notes where id = ?").execute([id]).first
7
- return nil unless note_data
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
- cards_data = sql_able.prepare("select * from cards where nid = ?").execute([id]).to_a
10
- { note_data: note_data, cards_data: cards_data }
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 SharedConstantsHelper # :nodoc:
5
- NEW_OBJECT_USN = -1
6
- NON_FILTERED_DECK_DYN = 0
7
- private_constant :NEW_OBJECT_USN, :NON_FILTERED_DECK_DYN
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
- # Returns approximately the number of milliseconds since the 1970 epoch.
13
- # A random amount of milliseconds between -5000 and 5000 is added so that
14
- # primary key ids calculated with this should be unique.
15
- def milliseconds_since_epoch
16
- DateTime.now.strftime("%Q").to_i + rand(-5000..5000)
17
- end
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
- # Returns approximately the number of seconds since the 1970 epoch.
21
- def seconds_since_epoch
22
- Time.now.to_i
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
@@ -9,18 +9,15 @@ require_relative "note_attributes"
9
9
  # rubocop:disable Metrics/ClassLength
10
10
  module AnkiRecord
11
11
  ##
12
- # Represents an Anki note. The note object corresponds to a record in the `notes`
13
- # table in the collection.anki21 database.
12
+ # Represents an Anki note.
14
13
  class Note
15
- include ChecksumHelper
14
+ include Helpers::ChecksumHelper
16
15
  include NoteAttributes
17
- include TimeHelper
18
- include SharedConstantsHelper
16
+ include Helpers::TimeHelper
17
+ include Helpers::SharedConstantsHelper
19
18
 
20
19
  ##
21
20
  # 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
21
  def initialize(note_type: nil, deck: nil, collection: nil, data: nil)
25
22
  if note_type && deck
26
23
  setup_instance_variables_for_new_note(note_type: note_type, deck: deck)
@@ -13,10 +13,10 @@ module AnkiRecord
13
13
  #
14
14
  # The attributes are documented in the NoteTypeAttributes module.
15
15
  class NoteType
16
- include AnkiRecord::SharedConstantsHelper
17
- include AnkiRecord::TimeHelper
18
- include AnkiRecord::NoteTypeAttributes
19
- include AnkiRecord::NoteTypeDefaults
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AnkiRecord
4
- VERSION = "0.3.0" # :nodoc:
4
+ VERSION = "0.3.1" # :nodoc:
5
5
  end
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.0
4
+ version: 0.3.1
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-03-26 00:00:00.000000000 Z
11
+ date: 2023-04-29 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