anki_record 0.3.2 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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 +15 -1
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +9 -2
  7. data/README.md +89 -132
  8. data/anki_record.gemspec +2 -2
  9. data/lib/anki_record/anki21_database/anki21_database.rb +172 -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 +115 -174
  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 +72 -93
  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