anki_record 0.3.2 → 0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rubocop.yml +2 -7
  4. data/CHANGELOG.md +9 -1
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +9 -2
  7. data/README.md +79 -132
  8. data/anki_record.gemspec +2 -2
  9. data/lib/anki_record/anki21_database/anki21_database.rb +138 -0
  10. data/lib/anki_record/anki21_database/anki21_database_attributes.rb +31 -0
  11. data/lib/anki_record/anki21_database/anki21_database_constructors.rb +52 -0
  12. data/lib/anki_record/anki2_database/anki2_database.rb +44 -0
  13. data/lib/anki_record/anki_package/anki_package.rb +110 -176
  14. data/lib/anki_record/card/card.rb +17 -33
  15. data/lib/anki_record/card/card_attributes.rb +3 -34
  16. data/lib/anki_record/card_template/card_template.rb +2 -2
  17. data/lib/anki_record/card_template/card_template_attributes.rb +11 -11
  18. data/lib/anki_record/collection/collection.rb +20 -154
  19. data/lib/anki_record/collection/collection_attributes.rb +2 -30
  20. data/lib/anki_record/deck/deck.rb +11 -10
  21. data/lib/anki_record/deck/deck_attributes.rb +6 -8
  22. data/lib/anki_record/deck/deck_defaults.rb +1 -1
  23. data/lib/anki_record/deck_options_group/deck_options_group.rb +8 -6
  24. data/lib/anki_record/deck_options_group/deck_options_group_attributes.rb +5 -7
  25. data/lib/anki_record/helpers/anki_guid_helper.rb +20 -0
  26. data/lib/anki_record/media/media.rb +36 -0
  27. data/lib/anki_record/note/note.rb +62 -86
  28. data/lib/anki_record/note/note_attributes.rb +18 -17
  29. data/lib/anki_record/note_field/note_field.rb +3 -3
  30. data/lib/anki_record/note_field/note_field_attributes.rb +9 -9
  31. data/lib/anki_record/note_type/note_type.rb +13 -14
  32. data/lib/anki_record/note_type/note_type_attributes.rb +17 -21
  33. data/lib/anki_record/version.rb +1 -1
  34. metadata +11 -7
  35. data/lib/anki_record/helpers/data_query_helper.rb +0 -15
  36. data/lib/anki_record/note/note_guid_helper.rb +0 -10
@@ -4,7 +4,6 @@ require "json"
4
4
 
5
5
  require_relative "../deck/deck"
6
6
  require_relative "../deck_options_group/deck_options_group"
7
- require_relative "../helpers/data_query_helper"
8
7
  require_relative "../helpers/time_helper"
9
8
  require_relative "../note_type/note_type"
10
9
  require_relative "collection_attributes"
@@ -12,171 +11,38 @@ require_relative "collection_attributes"
12
11
  module AnkiRecord
13
12
  ##
14
13
  # Collection represents the single record in the Anki collection.anki21 database's `col` table.
15
- # The note types, decks, and deck options groups data are contained within this record.
14
+ # The note types, decks, and deck options groups data are contained within this record, but
15
+ # for simplicity of the gem's API, they are managed by the Anki21Database class.
16
16
  class Collection
17
- include Helpers::DataQueryHelper
18
17
  include Helpers::TimeHelper
19
18
  include CollectionAttributes
20
19
 
21
- def initialize(anki_package:) # :nodoc:
22
- setup_collection_instance_variables(anki_package: anki_package)
23
- end
24
-
25
- def add_note_type(note_type) # :nodoc:
26
- raise ArgumentError unless note_type.instance_of?(AnkiRecord::NoteType)
27
-
28
- existing_note_type = nil
29
- @note_types.each do |nt|
30
- existing_note_type = nt if nt.id == note_type.id
31
- end
32
- @note_types.delete(existing_note_type) if existing_note_type
33
-
34
- @note_types << note_type
35
- end
36
-
37
- def add_deck(deck) # :nodoc:
38
- raise ArgumentError unless deck.instance_of?(AnkiRecord::Deck)
39
-
40
- @decks << deck
41
- end
42
-
43
- def add_deck_options_group(deck_options_group) # :nodoc:
44
- raise ArgumentError unless deck_options_group.instance_of?(AnkiRecord::DeckOptionsGroup)
45
-
46
- @deck_options_groups << deck_options_group
47
- end
48
-
49
- ##
50
- # Returns the collection's note type object found by either +name+ or +id+, or nil if it is not found.
51
- def find_note_type_by(name: nil, id: nil)
52
- if (id && name) || (id.nil? && name.nil?)
53
- raise ArgumentError,
54
- "You must pass either an id or name keyword argument."
55
- end
56
-
57
- name ? find_note_type_by_name(name: name) : find_note_type_by_id(id: id)
58
- end
59
-
60
- private
61
-
62
- def find_note_type_by_name(name:)
63
- note_types.find { |note_type| note_type.name == name }
64
- end
65
-
66
- def find_note_type_by_id(id:)
67
- note_types.find { |note_type| note_type.id == id }
68
- end
69
-
70
- public
71
-
72
- ##
73
- # Returns the collection's deck object found by either +name+ or +id+, or nil if it is not found.
74
- def find_deck_by(name: nil, id: nil)
75
- if (id && name) || (id.nil? && name.nil?)
76
- raise ArgumentError,
77
- "You must pass either an id or name keyword argument."
78
- end
79
-
80
- name ? find_deck_by_name(name: name) : find_deck_by_id(id: id)
81
- end
82
-
83
- private
84
-
85
- def find_deck_by_name(name:)
86
- decks.find { |deck| deck.name == name }
87
- end
88
-
89
- def find_deck_by_id(id:)
90
- decks.find { |deck| deck.id == id }
91
- end
92
-
93
- public
94
-
95
- ##
96
- # Returns the collection's deck options group object found by +id+, or nil if it is not found.
97
- def find_deck_options_group_by(id:)
98
- deck_options_groups.find { |deck_options_group| deck_options_group.id == id }
99
- end
100
-
101
- ##
102
- # Returns the collection's note object found by +id+, or nil if it is not found.
103
- def find_note_by(id:)
104
- note_cards_data = note_cards_data_for_note_id sql_able: anki_package, id: id
105
- return nil unless note_cards_data
106
-
107
- AnkiRecord::Note.new collection: self, data: note_cards_data
108
- end
109
-
110
- # :nodoc:
111
- def decks_json
112
- JSON.parse(anki_package.prepare("select decks from col;").execute.first["decks"])
113
- end
20
+ attr_reader :anki21_database
114
21
 
22
+ # rubocop:disable Metrics/AbcSize
23
+ # rubocop:disable Metrics/MethodLength
115
24
  # :nodoc:
116
- def models_json
117
- JSON.parse(anki_package.prepare("select models from col;").execute.first["models"])
118
- end
119
-
120
- def copy_over_existing(col_record:) # :nodoc:
121
- @col_record = col_record
122
- setup_simple_collaborator_objects
123
- setup_custom_collaborator_objects
25
+ def initialize(anki21_database:)
26
+ @anki21_database = anki21_database
27
+ @id = col_record["id"]
28
+ @created_at_timestamp = col_record["crt"]
29
+ @last_modified_timestamp = col_record["mod"]
30
+ @scm = col_record["scm"]
31
+ @ver = col_record["ver"]
32
+ @dty = col_record["dty"]
33
+ @usn = col_record["usn"]
34
+ @ls = col_record["ls"]
35
+ @configuration = JSON.parse(col_record["conf"])
36
+ @tags = JSON.parse(col_record["tags"])
124
37
  remove_instance_variable(:@col_record)
125
38
  end
39
+ # rubocop:enable Metrics/AbcSize
40
+ # rubocop:enable Metrics/MethodLength
126
41
 
127
42
  private
128
43
 
129
- def setup_collection_instance_variables(anki_package:)
130
- @anki_package = anki_package
131
- setup_simple_collaborator_objects
132
- setup_custom_collaborator_objects
133
- remove_instance_variable(:@col_record)
134
- end
135
-
136
44
  def col_record
137
- @col_record ||= @anki_package.prepare("select * from col").execute.first
138
- end
139
-
140
- # rubocop:disable Metrics/AbcSize
141
- def setup_simple_collaborator_objects
142
- @id = col_record["id"]
143
- @created_at_timestamp = col_record["crt"]
144
- @last_modified_timestamp = col_record["mod"]
145
- @scm = col_record["scm"]
146
- @ver = col_record["ver"]
147
- @dty = col_record["dty"]
148
- @usn = col_record["usn"]
149
- @ls = col_record["ls"]
150
- @configuration = JSON.parse(col_record["conf"])
151
- @tags = JSON.parse(col_record["tags"])
152
- end
153
- # rubocop:enable Metrics/AbcSize
154
-
155
- def setup_custom_collaborator_objects
156
- setup_note_type_collaborators
157
- setup_deck_options_groups_collaborators
158
- setup_deck_collaborators
159
- end
160
-
161
- def setup_note_type_collaborators
162
- @note_types = []
163
- JSON.parse(col_record["models"]).values.map do |model_hash|
164
- NoteType.new(collection: self, args: model_hash)
165
- end
166
- end
167
-
168
- def setup_deck_collaborators
169
- @decks = []
170
- JSON.parse(col_record["decks"]).values.map do |deck_hash|
171
- Deck.new(collection: self, args: deck_hash)
172
- end
173
- end
174
-
175
- def setup_deck_options_groups_collaborators
176
- @deck_options_groups = []
177
- JSON.parse(col_record["dconf"]).values.map do |dconf_hash|
178
- DeckOptionsGroup.new(collection: self, args: dconf_hash)
179
- end
45
+ @col_record ||= anki21_database.col_record
180
46
  end
181
47
  end
182
48
  end
@@ -1,35 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AnkiRecord
4
- ##
5
- # Module with the Collection class's attribute readers, writers, and accessors.
6
- module CollectionAttributes
7
- ##
8
- # The collection's Anki package object.
9
- attr_reader :anki_package
10
-
11
- ##
12
- # The collection's id, which is also the id of the col record in the collection.anki21 database (usually 1).
13
- attr_reader :id
14
-
15
- ##
16
- # The number of milliseconds since the 1970 epoch when the collection record was created.
17
- attr_reader :created_at_timestamp
18
-
19
- ##
20
- # The number of milliseconds since the 1970 epoch at which the collection record was last modified.
21
- attr_reader :last_modified_timestamp
22
-
23
- ##
24
- # The collection's note type objects as an array.
25
- attr_reader :note_types
26
-
27
- ##
28
- # The collection's deck objects as an array
29
- attr_reader :decks
30
-
31
- ##
32
- # The collection's deck option group objects as an array.
33
- attr_reader :deck_options_groups
4
+ module CollectionAttributes # :nodoc:
5
+ attr_reader :id, :created_at_timestamp, :last_modified_timestamp
34
6
  end
35
7
  end
@@ -15,28 +15,28 @@ module AnkiRecord
15
15
  include Helpers::TimeHelper
16
16
 
17
17
  ##
18
- # Instantiates a new Deck object belonging to +collection+ with name +name+.
19
- def initialize(collection:, name: nil, args: nil)
18
+ # Instantiates a new Deck object belonging to +anki21_database+ with name +name+.
19
+ def initialize(anki21_database:, name: nil, args: nil)
20
20
  raise ArgumentError unless (name && args.nil?) || (args && args["name"])
21
21
 
22
- @collection = collection
22
+ @anki21_database = anki21_database
23
23
  if args
24
- setup_deck_instance_variables_from_existing(args: args)
24
+ setup_deck_instance_variables_from_existing(args:)
25
25
  else
26
- setup_deck_instance_variables(name: name)
26
+ setup_deck_instance_variables(name:)
27
27
  end
28
28
 
29
- @collection.add_deck self
29
+ @anki21_database.add_deck self
30
30
  save if args
31
31
  end
32
32
 
33
33
  ##
34
34
  # Saves the deck to the collection.anki21 database.
35
35
  def save
36
- collection_decks_hash = collection.decks_json
36
+ collection_decks_hash = anki21_database.decks_json
37
37
  collection_decks_hash[@id] = to_h
38
38
  sql = "update col set decks = ? where id = ?"
39
- collection.anki_package.prepare(sql).execute([JSON.generate(collection_decks_hash), collection.id])
39
+ anki21_database.prepare(sql).execute([JSON.generate(collection_decks_hash), anki21_database.collection.id])
40
40
  end
41
41
 
42
42
  def to_h # :nodoc:
@@ -54,6 +54,7 @@ module AnkiRecord
54
54
  def inspect
55
55
  "#<AnkiRecord::Deck:#{object_id} id: #{id} name: #{name} description: #{description}>"
56
56
  end
57
+ # :nocov:
57
58
 
58
59
  private
59
60
 
@@ -72,7 +73,7 @@ module AnkiRecord
72
73
  @collapsed_in_browser = args["browserCollapsed"]
73
74
  @description = args["desc"]
74
75
  @dyn = args["dyn"]
75
- @deck_options_group = @collection.find_deck_options_group_by id: args["conf"]
76
+ @deck_options_group = anki21_database.find_deck_options_group_by(id: args["conf"])
76
77
  @extend_new = args["extendNew"]
77
78
  @extend_review = args["extendRev"]
78
79
  end
@@ -90,7 +91,7 @@ module AnkiRecord
90
91
  @collapsed_in_browser = default_collapsed
91
92
  @description = ""
92
93
  @dyn = NON_FILTERED_DECK_DYN
93
- @deck_options_group = @collection.find_deck_options_group_by id: default_deck_options_group_id
94
+ @deck_options_group = anki21_database.find_deck_options_group_by id: default_deck_options_group_id
94
95
  @extend_new = 0
95
96
  @extend_review = 0
96
97
  end
@@ -3,28 +3,26 @@
3
3
  module AnkiRecord
4
4
  # Module with the Deck class's attribute readers, writers, and accessors.
5
5
  module DeckAttributes
6
- ##
7
- # The deck's collection object.
8
- attr_reader :collection
6
+ attr_reader :anki21_database # :nodoc:
9
7
 
10
8
  ##
11
- # The deck's name.
9
+ # The deck's name
12
10
  attr_accessor :name
13
11
 
14
12
  ##
15
- # The deck's description.
13
+ # The deck's description
16
14
  attr_accessor :description
17
15
 
18
16
  ##
19
- # The deck's id.
17
+ # The deck's id
20
18
  attr_reader :id
21
19
 
22
20
  ##
23
- # The number of seconds since the 1970 epoch when the deck was last modified.
21
+ # The number of seconds since the 1970 epoch when the deck was last modified
24
22
  attr_reader :last_modified_timestamp
25
23
 
26
24
  ##
27
- # The deck's deck options group object.
25
+ # The deck's deck options group
28
26
  attr_reader :deck_options_group
29
27
  end
30
28
  end
@@ -5,7 +5,7 @@ module AnkiRecord
5
5
  private
6
6
 
7
7
  def default_deck_options_group_id
8
- collection.deck_options_groups.min_by(&:id).id
8
+ anki21_database.deck_options_groups.min_by(&:id).id
9
9
  end
10
10
 
11
11
  def default_deck_today_array
@@ -7,25 +7,27 @@ require_relative "deck_options_group_attributes"
7
7
  module AnkiRecord
8
8
  ##
9
9
  # DeckOptionsGroup represents a set of options that can be applied to an Anki deck.
10
+ # The interface to this has not been explored much so using the gem with these directly
11
+ # may involve breaking encapsulation.
10
12
  class DeckOptionsGroup
11
13
  include DeckOptionsGroupAttributes
12
14
  include Helpers::SharedConstantsHelper
13
15
  include Helpers::TimeHelper
14
16
 
15
17
  ##
16
- # Instantiates a new deck options group belonging to +collection+ with name +name+.
17
- def initialize(collection:, name: nil, args: nil)
18
+ # Instantiates a new deck options group belonging to +anki21_database+ with name +name+.
19
+ def initialize(anki21_database:, name: nil, args: nil)
18
20
  raise ArgumentError unless (name && args.nil?) || (args && args["name"])
19
21
 
20
- @collection = collection
22
+ @anki21_database = anki21_database
21
23
 
22
24
  if args
23
- setup_deck_options_group_instance_variables_from_existing(args: args)
25
+ setup_deck_options_group_instance_variables_from_existing(args:)
24
26
  else
25
- setup_deck_options_group_instance_variables(name: name)
27
+ setup_deck_options_group_instance_variables(name:)
26
28
  end
27
29
 
28
- @collection.add_deck_options_group self
30
+ @anki21_database.add_deck_options_group self
29
31
  end
30
32
 
31
33
  private
@@ -2,22 +2,20 @@
2
2
 
3
3
  module AnkiRecord
4
4
  ##
5
- # Module with the Card class's attribute readers, writers, and accessors.
5
+ # Module with DeckOptionsGroup's attribute readers, writers, and accessors.
6
6
  module DeckOptionsGroupAttributes
7
- ##
8
- # The deck options group's collection object.
9
- attr_reader :collection
7
+ attr_reader :anki21_database # :nodoc:
10
8
 
11
9
  ##
12
- # The deck option group's name.
10
+ # The deck option group's name
13
11
  attr_accessor :name
14
12
 
15
13
  ##
16
- # The deck option group's id.
14
+ # The deck option group's id
17
15
  attr_reader :id
18
16
 
19
17
  ##
20
- # The number of milliseconds since the 1970 epoch at which the deck options group was modified.
18
+ # The number of milliseconds since the 1970 epoch at which the deck options group was modified
21
19
  attr_reader :last_modified_timestamp
22
20
  end
23
21
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module AnkiRecord
6
+ module Helpers
7
+ ##
8
+ # A module for the method that computes the guid value of notes.
9
+ #
10
+ # This guid is used by Anki when importing a deck package to update existing notes
11
+ # rather than create duplicates of them.
12
+ module AnkiGuidHelper
13
+ ##
14
+ # Returns a random string of 10 characters sliced out of a random base64 string (RFC 3548).
15
+ def self.globally_unique_id
16
+ SecureRandom.base64(9).slice(1, 10)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnkiRecord
4
+ class Media # :nodoc:
5
+ attr_reader :anki_package, :media_file
6
+
7
+ FILENAME = "media"
8
+
9
+ def self.create_new(anki_package:)
10
+ media = new
11
+ media.create_initialize(anki_package:)
12
+ media
13
+ end
14
+
15
+ def create_initialize(anki_package:)
16
+ @anki_package = anki_package
17
+ media_file_path = FileUtils.touch("#{anki_package.tmpdir}/#{FILENAME}")[0]
18
+ @media_file = File.open(media_file_path, mode: "w")
19
+ media_file.write("{}")
20
+ media_file.close
21
+ end
22
+
23
+ def self.update_new(anki_package:)
24
+ media = new
25
+ media.update_initialize(anki_package:)
26
+ media
27
+ end
28
+
29
+ def update_initialize(anki_package:)
30
+ @anki_package = anki_package
31
+ @media_file = File.open("#{anki_package.tmpdir}/#{FILENAME}", mode: "w")
32
+ media_file.write("{}")
33
+ media_file.close
34
+ end
35
+ end
36
+ end