anki_record 0.2.0 → 0.3.1

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 (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