anki_record 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +13 -2
  3. data/CHANGELOG.md +37 -9
  4. data/Gemfile +3 -1
  5. data/Gemfile.lock +10 -2
  6. data/README.md +120 -35
  7. data/anki_record.gemspec +1 -5
  8. data/lib/anki_record/anki_package/anki_package.rb +237 -0
  9. data/lib/anki_record/card/card.rb +108 -0
  10. data/lib/anki_record/card/card_attributes.rb +39 -0
  11. data/lib/anki_record/card_template/card_template.rb +64 -0
  12. data/lib/anki_record/card_template/card_template_attributes.rb +69 -0
  13. data/lib/anki_record/collection/collection.rb +182 -0
  14. data/lib/anki_record/collection/collection_attributes.rb +35 -0
  15. data/lib/anki_record/database_setup_constants.rb +88 -0
  16. data/lib/anki_record/deck/deck.rb +99 -0
  17. data/lib/anki_record/deck/deck_attributes.rb +30 -0
  18. data/lib/anki_record/deck/deck_defaults.rb +19 -0
  19. data/lib/anki_record/{deck_options_group.rb → deck_options_group/deck_options_group.rb} +12 -31
  20. data/lib/anki_record/deck_options_group/deck_options_group_attributes.rb +23 -0
  21. data/lib/anki_record/helpers/checksum_helper.rb +10 -11
  22. data/lib/anki_record/helpers/data_query_helper.rb +15 -0
  23. data/lib/anki_record/helpers/shared_constants_helper.rb +6 -6
  24. data/lib/anki_record/helpers/time_helper.rb +18 -13
  25. data/lib/anki_record/note/note.rb +178 -0
  26. data/lib/anki_record/note/note_attributes.rb +56 -0
  27. data/lib/anki_record/note_field/note_field.rb +62 -0
  28. data/lib/anki_record/note_field/note_field_attributes.rb +39 -0
  29. data/lib/anki_record/note_field/note_field_defaults.rb +19 -0
  30. data/lib/anki_record/note_type/note_type.rb +161 -0
  31. data/lib/anki_record/note_type/note_type_attributes.rb +80 -0
  32. data/lib/anki_record/note_type/note_type_defaults.rb +38 -0
  33. data/lib/anki_record/version.rb +1 -1
  34. data/lib/anki_record.rb +1 -16
  35. metadata +26 -16
  36. data/lib/anki_record/anki_package.rb +0 -194
  37. data/lib/anki_record/card.rb +0 -75
  38. data/lib/anki_record/card_template.rb +0 -105
  39. data/lib/anki_record/collection.rb +0 -105
  40. data/lib/anki_record/db/anki_schema_definition.rb +0 -77
  41. data/lib/anki_record/db/clean_collection21_record.rb +0 -10
  42. data/lib/anki_record/db/clean_collection2_record.rb +0 -10
  43. data/lib/anki_record/deck.rb +0 -101
  44. data/lib/anki_record/note.rb +0 -135
  45. data/lib/anki_record/note_field.rb +0 -84
  46. data/lib/anki_record/note_type.rb +0 -233
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "deck_attributes"
4
+ require_relative "deck_defaults"
5
+ require_relative "../helpers/shared_constants_helper"
6
+ require_relative "../helpers/time_helper"
7
+
8
+ module AnkiRecord
9
+ ##
10
+ # Deck represents an Anki deck.
11
+ class Deck
12
+ include DeckAttributes
13
+ include DeckDefaults
14
+ include Helpers::SharedConstantsHelper
15
+ include Helpers::TimeHelper
16
+
17
+ ##
18
+ # Instantiates a new Deck object belonging to +collection+ with name +name+.
19
+ def initialize(collection:, name: nil, args: nil)
20
+ raise ArgumentError unless (name && args.nil?) || (args && args["name"])
21
+
22
+ @collection = collection
23
+ if args
24
+ setup_deck_instance_variables_from_existing(args: args)
25
+ else
26
+ setup_deck_instance_variables(name: name)
27
+ end
28
+
29
+ @collection.add_deck self
30
+ save if args
31
+ end
32
+
33
+ ##
34
+ # Saves the deck to the collection.anki21 database.
35
+ def save
36
+ collection_decks_hash = collection.decks_json
37
+ collection_decks_hash[@id] = to_h
38
+ sql = "update col set decks = ? where id = ?"
39
+ collection.anki_package.prepare(sql).execute([JSON.generate(collection_decks_hash), collection.id])
40
+ end
41
+
42
+ def to_h # :nodoc:
43
+ {
44
+ id: @id, mod: @last_modified_timestamp, name: @name, usn: @usn,
45
+ lrnToday: @learn_today, revToday: @review_today, newToday: @new_today, timeToday: @time_today,
46
+ collapsed: @collapsed_in_main_window, browserCollapsed: @collapsed_in_browser,
47
+ desc: @description, dyn: @dyn, conf: @deck_options_group.id,
48
+ extendNew: @extend_new, extendRev: @extend_review
49
+ }
50
+ end
51
+
52
+ # :nodoc:
53
+ # :nocov:
54
+ def inspect
55
+ "#<AnkiRecord::Deck:#{object_id} id: #{id} name: #{name} description: #{description}>"
56
+ end
57
+
58
+ private
59
+
60
+ # rubocop:disable Metrics/MethodLength
61
+ # rubocop:disable Metrics/AbcSize
62
+ def setup_deck_instance_variables_from_existing(args:)
63
+ @id = args["id"]
64
+ @last_modified_timestamp = args["mod"]
65
+ @name = args["name"]
66
+ @usn = args["usn"]
67
+ @learn_today = args["lrnToday"]
68
+ @review_today = args["revToday"]
69
+ @new_today = args["newToday"]
70
+ @time_today = args["timeToday"]
71
+ @collapsed_in_main_window = args["collapsed"]
72
+ @collapsed_in_browser = args["browserCollapsed"]
73
+ @description = args["desc"]
74
+ @dyn = args["dyn"]
75
+ @deck_options_group = @collection.find_deck_options_group_by id: args["conf"]
76
+ @extend_new = args["extendNew"]
77
+ @extend_review = args["extendRev"]
78
+ end
79
+ # rubocop:enable Metrics/MethodLength
80
+ # rubocop:enable Metrics/AbcSize
81
+
82
+ # rubocop:disable Metrics/MethodLength
83
+ def setup_deck_instance_variables(name:)
84
+ @id = milliseconds_since_epoch
85
+ @last_modified_timestamp = seconds_since_epoch
86
+ @name = name
87
+ @usn = NEW_OBJECT_USN
88
+ @learn_today = @review_today = @new_today = @time_today = default_deck_today_array
89
+ @collapsed_in_main_window = default_collapsed
90
+ @collapsed_in_browser = default_collapsed
91
+ @description = ""
92
+ @dyn = NON_FILTERED_DECK_DYN
93
+ @deck_options_group = @collection.find_deck_options_group_by id: default_deck_options_group_id
94
+ @extend_new = 0
95
+ @extend_review = 0
96
+ end
97
+ # rubocop:enable Metrics/MethodLength
98
+ end
99
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnkiRecord
4
+ # Module with the Deck class's attribute readers, writers, and accessors.
5
+ module DeckAttributes
6
+ ##
7
+ # The deck's collection object.
8
+ attr_reader :collection
9
+
10
+ ##
11
+ # The deck's name.
12
+ attr_accessor :name
13
+
14
+ ##
15
+ # The deck's description.
16
+ attr_accessor :description
17
+
18
+ ##
19
+ # The deck's id.
20
+ attr_reader :id
21
+
22
+ ##
23
+ # The number of seconds since the 1970 epoch when the deck was last modified.
24
+ attr_reader :last_modified_timestamp
25
+
26
+ ##
27
+ # The deck's deck options group object.
28
+ attr_reader :deck_options_group
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnkiRecord
4
+ module DeckDefaults # :nodoc:
5
+ private
6
+
7
+ def default_deck_options_group_id
8
+ collection.deck_options_groups.min_by(&:id).id
9
+ end
10
+
11
+ def default_deck_today_array
12
+ [0, 0].freeze
13
+ end
14
+
15
+ def default_collapsed
16
+ false
17
+ end
18
+ end
19
+ end
@@ -1,37 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pry"
4
-
5
- require_relative "helpers/shared_constants_helper"
6
- require_relative "helpers/time_helper"
3
+ require_relative "../helpers/shared_constants_helper"
4
+ require_relative "../helpers/time_helper"
5
+ require_relative "deck_options_group_attributes"
7
6
 
8
7
  module AnkiRecord
9
8
  ##
10
- # Represents the set of options that can be applied to a deck
9
+ # DeckOptionsGroup represents a set of options that can be applied to an Anki deck.
11
10
  class DeckOptionsGroup
12
- include SharedConstantsHelper
13
- include TimeHelper
14
-
15
- ##
16
- # The collection object that the deck options group belongs to
17
- attr_reader :collection
18
-
19
- ##
20
- # The name of the deck options group
21
- attr_accessor :name
22
-
23
- ##
24
- # The id of the deck options group
25
- attr_reader :id
26
-
27
- ##
28
- # The last time that this deck options group was modified in milliseconds since the 1970 epoch
29
- attr_reader :last_modified_time
11
+ include DeckOptionsGroupAttributes
12
+ include Helpers::SharedConstantsHelper
13
+ include Helpers::TimeHelper
30
14
 
31
15
  ##
32
- # Instantiates a new deck options group called +name+ with defaults
16
+ # Instantiates a new deck options group belonging to +collection+ with name +name+.
33
17
  def initialize(collection:, name: nil, args: nil)
34
- # TODO: extract this check to a shared helper
35
18
  raise ArgumentError unless (name && args.nil?) || (args && args["name"])
36
19
 
37
20
  @collection = collection
@@ -41,6 +24,8 @@ module AnkiRecord
41
24
  else
42
25
  setup_deck_options_group_instance_variables(name: name)
43
26
  end
27
+
28
+ @collection.add_deck_options_group self
44
29
  end
45
30
 
46
31
  private
@@ -49,7 +34,7 @@ module AnkiRecord
49
34
  # rubocop:disable Metrics/AbcSize
50
35
  def setup_deck_options_group_instance_variables_from_existing(args:)
51
36
  @id = args["id"]
52
- @last_modified_time = args["mod"]
37
+ @last_modified_timestamp = args["mod"]
53
38
  @name = args["name"]
54
39
  @usn = args["usn"]
55
40
  @max_taken = args["maxTaken"]
@@ -68,14 +53,10 @@ module AnkiRecord
68
53
  @new_gather_priority = args["newGatherPriority"]
69
54
  @bury_interday_learning = args["buryInterdayLearning"]
70
55
  end
71
- # rubocop:enable Metrics/AbcSize
72
- # rubocop:enable Metrics/MethodLength
73
56
 
74
- # rubocop:disable Metrics/MethodLength
75
- # rubocop:disable Metrics/AbcSize
76
57
  def setup_deck_options_group_instance_variables(name:)
77
58
  @id = milliseconds_since_epoch
78
- @last_modified_time = seconds_since_epoch
59
+ @last_modified_timestamp = seconds_since_epoch
79
60
  @name = name
80
61
  @usn = NEW_OBJECT_USN
81
62
  @max_taken = 60
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnkiRecord
4
+ ##
5
+ # Module with the Card class's attribute readers, writers, and accessors.
6
+ module DeckOptionsGroupAttributes
7
+ ##
8
+ # The deck options group's collection object.
9
+ attr_reader :collection
10
+
11
+ ##
12
+ # The deck option group's name.
13
+ attr_accessor :name
14
+
15
+ ##
16
+ # The deck option group's id.
17
+ attr_reader :id
18
+
19
+ ##
20
+ # The number of milliseconds since the 1970 epoch at which the deck options group was modified.
21
+ attr_reader :last_modified_timestamp
22
+ end
23
+ end
@@ -3,18 +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 is used by Anki to detect duplicates.
10
- module ChecksumHelper
6
+ module Helpers
11
7
  ##
12
- # Compute the integer representation of the first 8 characters of the digest
13
- # (calculated using the SHA-1 Secure Hash Algorithm) of the argument
14
- # TODO: This needs to be expanded to strip HTML (except media)
15
- # and more tests to ensure it calculates the same value as Anki does in that case
16
- def checksum(sfld)
17
- 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
18
17
  end
19
18
  end
20
19
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnkiRecord
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
9
+
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
13
+ end
14
+ end
15
+ end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AnkiRecord
4
- ##
5
- # Helper module to hold the constants used by multiple classes
6
- module SharedConstantsHelper
7
- NEW_OBJECT_USN = -1
8
- NON_FILTERED_DECK_DYN = 0
9
- 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
10
10
  end
11
11
  end
@@ -3,21 +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
- # Return the number of milliseconds since the 1970 epoch
13
- def milliseconds_since_epoch
14
- DateTime.now.strftime("%Q").to_i
15
- 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
16
20
 
17
- ##
18
- # Return the number of seconds since the 1970 epoch
19
- def seconds_since_epoch
20
- 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
21
26
  end
22
27
  end
23
28
  end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ require_relative "../helpers/checksum_helper"
6
+ require_relative "../helpers/time_helper"
7
+ require_relative "note_attributes"
8
+
9
+ # rubocop:disable Metrics/ClassLength
10
+ module AnkiRecord
11
+ ##
12
+ # Represents an Anki note.
13
+ class Note
14
+ include Helpers::ChecksumHelper
15
+ include NoteAttributes
16
+ include Helpers::TimeHelper
17
+ include Helpers::SharedConstantsHelper
18
+
19
+ ##
20
+ # Instantiates a note of type +note_type+ and belonging to deck +deck+.
21
+ def initialize(note_type: nil, deck: nil, collection: nil, data: nil)
22
+ if note_type && deck
23
+ setup_instance_variables_for_new_note(note_type: note_type, deck: deck)
24
+ elsif collection && data
25
+ setup_instance_variables_from_existing(collection: collection,
26
+ note_data: data[:note_data], cards_data: data[:cards_data])
27
+ else
28
+ raise ArgumentError
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def setup_instance_variables_for_new_note(note_type:, deck:)
35
+ raise ArgumentError unless note_type.collection == deck.collection
36
+
37
+ setup_collaborator_object_instance_variables_for_new_note(note_type: note_type, deck: deck)
38
+ setup_simple_instance_variables_for_new_note
39
+ end
40
+
41
+ def setup_collaborator_object_instance_variables_for_new_note(note_type:, deck:)
42
+ @note_type = note_type
43
+ @deck = deck
44
+ @collection = deck.collection
45
+ @field_contents = setup_empty_field_contents_hash
46
+ @cards = @note_type.card_templates.map do |card_template|
47
+ Card.new(note: self, card_template: card_template)
48
+ end
49
+ end
50
+
51
+ def setup_simple_instance_variables_for_new_note
52
+ @id = milliseconds_since_epoch
53
+ @guid = globally_unique_id
54
+ @last_modified_timestamp = seconds_since_epoch
55
+ @usn = NEW_OBJECT_USN
56
+ @tags = []
57
+ @flags = 0
58
+ @data = ""
59
+ end
60
+
61
+ def setup_instance_variables_from_existing(collection:, note_data:, cards_data:)
62
+ setup_collaborator_object_instance_variables_from_existing(collection: collection, note_data: note_data,
63
+ cards_data: cards_data)
64
+ setup_simple_instance_variables_from_existing(note_data: note_data)
65
+ end
66
+
67
+ def setup_collaborator_object_instance_variables_from_existing(collection:, note_data:, cards_data:)
68
+ @collection = collection
69
+ @note_type = collection.find_note_type_by id: note_data["mid"]
70
+ @field_contents = setup_field_contents_hash_from_existing(note_data: note_data)
71
+ @cards = @note_type.card_templates.map.with_index do |_card_template, index|
72
+ Card.new(note: self, card_data: cards_data[index])
73
+ end
74
+ end
75
+
76
+ def setup_field_contents_hash_from_existing(note_data:)
77
+ field_contents = setup_empty_field_contents_hash
78
+ snake_case_field_names_in_order = note_type.snake_case_field_names
79
+ note_data["flds"].split("\x1F").each_with_index do |fld, ordinal|
80
+ field_contents[snake_case_field_names_in_order[ordinal]] = fld
81
+ end
82
+ field_contents
83
+ end
84
+
85
+ def setup_simple_instance_variables_from_existing(note_data:)
86
+ @id = note_data["id"]
87
+ @guid = note_data["guid"]
88
+ @last_modified_timestamp = note_data["mod"]
89
+ @usn = note_data["usn"]
90
+ @tags = note_data["tags"].split
91
+ @flags = note_data["flags"]
92
+ @data = note_data["data"]
93
+ end
94
+
95
+ def setup_empty_field_contents_hash
96
+ field_contents = {}
97
+ note_type.snake_case_field_names.each { |field_name| field_contents[field_name] = "" }
98
+ field_contents
99
+ end
100
+
101
+ public
102
+
103
+ ##
104
+ # Saves the note to the collection.anki21 database.
105
+ #
106
+ # This also saves the note's cards.
107
+ def save
108
+ collection.find_note_by(id: @id) ? update_note_in_collection_anki21 : insert_new_note_in_collection_anki21
109
+ true
110
+ end
111
+
112
+ private
113
+
114
+ def update_note_in_collection_anki21
115
+ statement = @collection.anki_package.prepare <<~SQL
116
+ update notes set guid = ?, mid = ?, mod = ?, usn = ?, tags = ?,
117
+ flds = ?, sfld = ?, csum = ?, flags = ?, data = ? where id = ?
118
+ SQL
119
+ statement.execute([@guid, note_type.id, @last_modified_timestamp,
120
+ @usn, @tags.join(" "), field_values_separated_by_us, sort_field_value,
121
+ checksum(sort_field_value), @flags, @data, @id])
122
+ cards.each { |card| card.save(note_exists_already: true) }
123
+ end
124
+
125
+ def insert_new_note_in_collection_anki21
126
+ statement = @collection.anki_package.prepare <<~SQL
127
+ insert into notes (id, guid, mid, mod, usn, tags, flds, sfld, csum, flags, data)
128
+ values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
129
+ SQL
130
+ statement.execute([@id, @guid, note_type.id, @last_modified_timestamp,
131
+ @usn, @tags.join(" "), field_values_separated_by_us, sort_field_value,
132
+ checksum(sort_field_value), @flags, @data])
133
+ cards.each(&:save)
134
+ end
135
+
136
+ public
137
+
138
+ ##
139
+ # Overrides BasicObject#method_missing and creates "ghost methods".
140
+ #
141
+ # The ghost methods are the setters and getters for the note field values.
142
+ def method_missing(method_name, field_content = nil)
143
+ raise NoMethodError, "##{method_name} is not defined or a ghost method" unless respond_to_missing? method_name
144
+
145
+ method_name = method_name.to_s
146
+ return @field_contents[method_name] unless method_name.end_with?("=")
147
+
148
+ @field_contents[method_name.chomp("=")] = field_content
149
+ end
150
+
151
+ ##
152
+ # This allows #respond_to? to be accurate for the ghost methods created by #method_missing.
153
+ def respond_to_missing?(method_name, *)
154
+ method_name = method_name.to_s
155
+ if method_name.end_with?("=")
156
+ note_type.snake_case_field_names.include?(method_name.chomp("="))
157
+ else
158
+ note_type.snake_case_field_names.include?(method_name)
159
+ end
160
+ end
161
+
162
+ private
163
+
164
+ def globally_unique_id
165
+ SecureRandom.uuid.slice(5...15)
166
+ end
167
+
168
+ def field_values_separated_by_us
169
+ # The ASCII control code represented by hexadecimal 1F is the Unit Separator (US)
170
+ note_type.snake_case_field_names.map { |field_name| @field_contents[field_name] }.join("\x1F")
171
+ end
172
+
173
+ def sort_field_value
174
+ @field_contents[note_type.snake_case_sort_field_name]
175
+ end
176
+ end
177
+ end
178
+ # rubocop:enable Metrics/ClassLength
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnkiRecord
4
+ # Module with the Note class's attribute readers, writers, and accessors.
5
+ module NoteAttributes
6
+ ##
7
+ # The note's id.
8
+ attr_reader :id
9
+
10
+ ##
11
+ # The note's globally unique id.
12
+ attr_reader :guid
13
+
14
+ ##
15
+ # The number of seconds since the 1970 epoch at which the note was last modified.
16
+ attr_reader :last_modified_timestamp
17
+
18
+ ##
19
+ # The note's update sequence number.
20
+ attr_reader :usn
21
+
22
+ ##
23
+ # The note's tags, as an array. The tags are strings.
24
+ attr_reader :tags
25
+
26
+ ##
27
+ # The note's field contents, as a hash.
28
+ attr_reader :field_contents
29
+
30
+ ##
31
+ # The note's deck object.
32
+ #
33
+ # When the note is saved, the cards will belong to this deck.
34
+ attr_reader :deck
35
+
36
+ ##
37
+ # The note's note type object.
38
+ attr_reader :note_type
39
+
40
+ ##
41
+ # The note's collection object.
42
+ attr_reader :collection
43
+
44
+ ##
45
+ # The note's card objects, as an array.
46
+ attr_reader :cards
47
+
48
+ ##
49
+ # Corresponds to the flags column in the collection.anki21 notes table.
50
+ attr_reader :flags
51
+
52
+ ##
53
+ # Corresponds to the data column in the collection.anki21 notes table.
54
+ attr_reader :data
55
+ end
56
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "note_field_attributes"
4
+ require_relative "note_field_defaults"
5
+
6
+ module AnkiRecord
7
+ ##
8
+ # NoteField represents a field of an Anki note type.
9
+ class NoteField
10
+ include NoteFieldAttributes
11
+ include NoteFieldDefaults
12
+
13
+ ##
14
+ # Instantiates a new field for the note type +note_type+ with name +name+.
15
+ def initialize(note_type:, name: nil, args: nil)
16
+ raise ArgumentError unless (name && args.nil?) || (args && args["name"])
17
+
18
+ @note_type = note_type
19
+ if args
20
+ setup_note_field_instance_variables_from_existing(args: args)
21
+ else
22
+ setup_note_field_instance_variables_for_new_field(name: name)
23
+ end
24
+
25
+ @note_type.add_note_field self
26
+ end
27
+
28
+ def to_h # :nodoc:
29
+ {
30
+ name: @name,
31
+ ord: @ordinal_number,
32
+ sticky: @sticky,
33
+ rtl: @right_to_left,
34
+ font: @font_style,
35
+ size: @font_size,
36
+ description: @description
37
+ }
38
+ end
39
+
40
+ private
41
+
42
+ def setup_note_field_instance_variables_from_existing(args:)
43
+ @name = args["name"]
44
+ @ordinal_number = args["ord"]
45
+ @sticky = args["sticky"]
46
+ @right_to_left = args["rtl"]
47
+ @font_style = args["font"]
48
+ @font_size = args["size"]
49
+ @description = args["description"]
50
+ end
51
+
52
+ def setup_note_field_instance_variables_for_new_field(name:)
53
+ @name = name
54
+ @ordinal_number = @note_type.note_fields.length
55
+ @sticky = false
56
+ @right_to_left = false
57
+ @font_style = default_field_font_style
58
+ @font_size = default_field_font_size
59
+ @description = default_field_description
60
+ end
61
+ end
62
+ end