anki_record 0.1.1 → 0.3.0

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/CHANGELOG.md +33 -4
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +5 -3
  6. data/README.md +147 -11
  7. data/anki_record.gemspec +2 -6
  8. data/lib/anki_record/anki_package/anki_package.rb +245 -0
  9. data/lib/anki_record/anki_package/database_setup_constants.rb +91 -0
  10. data/lib/anki_record/card/card.rb +108 -0
  11. data/lib/anki_record/card/card_attributes.rb +39 -0
  12. data/lib/anki_record/{card_template.rb → card_template/card_template.rb} +20 -47
  13. data/lib/anki_record/card_template/card_template_attributes.rb +69 -0
  14. data/lib/anki_record/collection/collection.rb +180 -0
  15. data/lib/anki_record/collection/collection_attributes.rb +35 -0
  16. data/lib/anki_record/deck/deck.rb +101 -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} +10 -23
  20. data/lib/anki_record/deck_options_group/deck_options_group_attributes.rb +23 -0
  21. data/lib/anki_record/helpers/checksum_helper.rb +17 -0
  22. data/lib/anki_record/helpers/data_query_helper.rb +13 -0
  23. data/lib/anki_record/helpers/shared_constants_helper.rb +1 -3
  24. data/lib/anki_record/helpers/time_helper.rb +7 -5
  25. data/lib/anki_record/note/note.rb +181 -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 -15
  35. metadata +32 -20
  36. data/.rdoc_options +0 -27
  37. data/lib/anki_record/anki_package.rb +0 -183
  38. data/lib/anki_record/collection.rb +0 -65
  39. data/lib/anki_record/db/anki_schema_definition.rb +0 -77
  40. data/lib/anki_record/db/clean_collection21_record.rb +0 -10
  41. data/lib/anki_record/db/clean_collection2_record.rb +0 -10
  42. data/lib/anki_record/deck.rb +0 -88
  43. data/lib/anki_record/note_field.rb +0 -63
  44. data/lib/anki_record/note_type.rb +0 -147
@@ -0,0 +1,101 @@
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
+ # 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
+ class Deck
14
+ include DeckAttributes
15
+ include DeckDefaults
16
+ include SharedConstantsHelper
17
+ include TimeHelper
18
+
19
+ ##
20
+ # Instantiates a new Deck object belonging to +collection+ with name +name+.
21
+ def initialize(collection:, name: nil, args: nil)
22
+ raise ArgumentError unless (name && args.nil?) || (args && args["name"])
23
+
24
+ @collection = collection
25
+ if args
26
+ setup_deck_instance_variables_from_existing(args: args)
27
+ else
28
+ setup_deck_instance_variables(name: name)
29
+ end
30
+
31
+ @collection.add_deck self
32
+ save
33
+ end
34
+
35
+ ##
36
+ # Saves the deck (or updates it) in the collection.anki21 database.
37
+ def save
38
+ collection_decks_hash = collection.decks_json
39
+ collection_decks_hash[@id] = to_h
40
+ sql = "update col set decks = ? where id = ?"
41
+ collection.anki_package.prepare(sql).execute([JSON.generate(collection_decks_hash), collection.id])
42
+ end
43
+
44
+ def to_h # :nodoc:
45
+ {
46
+ id: @id, mod: @last_modified_timestamp, name: @name, usn: @usn,
47
+ lrnToday: @learn_today, revToday: @review_today, newToday: @new_today, timeToday: @time_today,
48
+ collapsed: @collapsed_in_main_window, browserCollapsed: @collapsed_in_browser,
49
+ desc: @description, dyn: @dyn, conf: @deck_options_group.id,
50
+ extendNew: @extend_new, extendRev: @extend_review
51
+ }
52
+ end
53
+
54
+ # :nodoc:
55
+ # :nocov:
56
+ def inspect
57
+ "#<AnkiRecord::Deck:#{object_id} id: #{id} name: #{name} description: #{description}>"
58
+ end
59
+
60
+ private
61
+
62
+ # rubocop:disable Metrics/MethodLength
63
+ # rubocop:disable Metrics/AbcSize
64
+ def setup_deck_instance_variables_from_existing(args:)
65
+ @id = args["id"]
66
+ @last_modified_timestamp = args["mod"]
67
+ @name = args["name"]
68
+ @usn = args["usn"]
69
+ @learn_today = args["lrnToday"]
70
+ @review_today = args["revToday"]
71
+ @new_today = args["newToday"]
72
+ @time_today = args["timeToday"]
73
+ @collapsed_in_main_window = args["collapsed"]
74
+ @collapsed_in_browser = args["browserCollapsed"]
75
+ @description = args["desc"]
76
+ @dyn = args["dyn"]
77
+ @deck_options_group = @collection.find_deck_options_group_by id: args["conf"]
78
+ @extend_new = args["extendNew"]
79
+ @extend_review = args["extendRev"]
80
+ end
81
+ # rubocop:enable Metrics/MethodLength
82
+ # rubocop:enable Metrics/AbcSize
83
+
84
+ # rubocop:disable Metrics/MethodLength
85
+ def setup_deck_instance_variables(name:)
86
+ @id = milliseconds_since_epoch
87
+ @last_modified_timestamp = seconds_since_epoch
88
+ @name = name
89
+ @usn = NEW_OBJECT_USN
90
+ @learn_today = @review_today = @new_today = @time_today = default_deck_today_array
91
+ @collapsed_in_main_window = default_collapsed
92
+ @collapsed_in_browser = default_collapsed
93
+ @description = ""
94
+ @dyn = NON_FILTERED_DECK_DYN
95
+ @deck_options_group = @collection.find_deck_options_group_by id: default_deck_options_group_id
96
+ @extend_new = 0
97
+ @extend_review = 0
98
+ end
99
+ # rubocop:enable Metrics/MethodLength
100
+ end
101
+ 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,31 +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
11
+ include DeckOptionsGroupAttributes
12
12
  include SharedConstantsHelper
13
13
  include TimeHelper
14
14
 
15
15
  ##
16
- # The name of the options group
17
- attr_accessor :name
18
-
19
- ##
20
- # One of many attributes that is currently read-only and needs to be documented.
21
- attr_reader :collection, :id, :last_modified_time, :usn, :max_taken, :auto_play, :timer, :replay_question,
22
- :new_options, :review_options, :lapse_options, :dyn, :new_mix, :new_per_day_minimum,
23
- :interday_learning_mix, :review_order, :new_sort_order, :new_gather_priority, :bury_interday_learning
24
-
25
- ##
26
- # Instantiates a new deck options group called +name+ with defaults
16
+ # Instantiates a new deck options group belonging to +collection+ with name +name+.
27
17
  def initialize(collection:, name: nil, args: nil)
28
- # TODO: extract this check to a shared helper
29
18
  raise ArgumentError unless (name && args.nil?) || (args && args["name"])
30
19
 
31
20
  @collection = collection
@@ -35,6 +24,8 @@ module AnkiRecord
35
24
  else
36
25
  setup_deck_options_group_instance_variables(name: name)
37
26
  end
27
+
28
+ @collection.add_deck_options_group self
38
29
  end
39
30
 
40
31
  private
@@ -43,7 +34,7 @@ module AnkiRecord
43
34
  # rubocop:disable Metrics/AbcSize
44
35
  def setup_deck_options_group_instance_variables_from_existing(args:)
45
36
  @id = args["id"]
46
- @last_modified_time = args["mod"]
37
+ @last_modified_timestamp = args["mod"]
47
38
  @name = args["name"]
48
39
  @usn = args["usn"]
49
40
  @max_taken = args["maxTaken"]
@@ -62,14 +53,10 @@ module AnkiRecord
62
53
  @new_gather_priority = args["newGatherPriority"]
63
54
  @bury_interday_learning = args["buryInterdayLearning"]
64
55
  end
65
- # rubocop:enable Metrics/AbcSize
66
- # rubocop:enable Metrics/MethodLength
67
56
 
68
- # rubocop:disable Metrics/MethodLength
69
- # rubocop:disable Metrics/AbcSize
70
57
  def setup_deck_options_group_instance_variables(name:)
71
58
  @id = milliseconds_since_epoch
72
- @last_modified_time = seconds_since_epoch
59
+ @last_modified_timestamp = seconds_since_epoch
73
60
  @name = name
74
61
  @usn = NEW_OBJECT_USN
75
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
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
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
11
+ ##
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
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
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
8
+
9
+ cards_data = sql_able.prepare("select * from cards where nid = ?").execute([id]).to_a
10
+ { note_data: note_data, cards_data: cards_data }
11
+ end
12
+ end
13
+ end
@@ -1,9 +1,7 @@
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
4
+ module SharedConstantsHelper # :nodoc:
7
5
  NEW_OBJECT_USN = -1
8
6
  NON_FILTERED_DECK_DYN = 0
9
7
  private_constant :NEW_OBJECT_USN, :NON_FILTERED_DECK_DYN
@@ -4,18 +4,20 @@ require "date"
4
4
 
5
5
  module AnkiRecord
6
6
  ##
7
- # Helper module to calculate integer time values since the 1970 epoch
7
+ # Helper module to calculate integer time values since the 1970 epoch.
8
8
  #
9
- # Specifically, the time that has passed since 00:00:00 UTC Jan 1 1970
9
+ # Specifically, the time that has passed since 00:00:00 UTC Jan 1 1970.
10
10
  module TimeHelper
11
11
  ##
12
- # Return the number of milliseconds since the 1970 epoch
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.
13
15
  def milliseconds_since_epoch
14
- DateTime.now.strftime("%Q").to_i
16
+ DateTime.now.strftime("%Q").to_i + rand(-5000..5000)
15
17
  end
16
18
 
17
19
  ##
18
- # Return the number of seconds since the 1970 epoch
20
+ # Returns approximately the number of seconds since the 1970 epoch.
19
21
  def seconds_since_epoch
20
22
  Time.now.to_i
21
23
  end
@@ -0,0 +1,181 @@
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. The note object corresponds to a record in the `notes`
13
+ # table in the collection.anki21 database.
14
+ class Note
15
+ include ChecksumHelper
16
+ include NoteAttributes
17
+ include TimeHelper
18
+ include SharedConstantsHelper
19
+
20
+ ##
21
+ # 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
+ def initialize(note_type: nil, deck: nil, collection: nil, data: nil)
25
+ if note_type && deck
26
+ setup_instance_variables_for_new_note(note_type: note_type, deck: deck)
27
+ elsif collection && data
28
+ setup_instance_variables_from_existing(collection: collection,
29
+ note_data: data[:note_data], cards_data: data[:cards_data])
30
+ else
31
+ raise ArgumentError
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def setup_instance_variables_for_new_note(note_type:, deck:)
38
+ raise ArgumentError unless note_type.collection == deck.collection
39
+
40
+ setup_collaborator_object_instance_variables_for_new_note(note_type: note_type, deck: deck)
41
+ setup_simple_instance_variables_for_new_note
42
+ end
43
+
44
+ def setup_collaborator_object_instance_variables_for_new_note(note_type:, deck:)
45
+ @note_type = note_type
46
+ @deck = deck
47
+ @collection = deck.collection
48
+ @field_contents = setup_empty_field_contents_hash
49
+ @cards = @note_type.card_templates.map do |card_template|
50
+ Card.new(note: self, card_template: card_template)
51
+ end
52
+ end
53
+
54
+ def setup_simple_instance_variables_for_new_note
55
+ @id = milliseconds_since_epoch
56
+ @guid = globally_unique_id
57
+ @last_modified_timestamp = seconds_since_epoch
58
+ @usn = NEW_OBJECT_USN
59
+ @tags = []
60
+ @flags = 0
61
+ @data = ""
62
+ end
63
+
64
+ def setup_instance_variables_from_existing(collection:, note_data:, cards_data:)
65
+ setup_collaborator_object_instance_variables_from_existing(collection: collection, note_data: note_data,
66
+ cards_data: cards_data)
67
+ setup_simple_instance_variables_from_existing(note_data: note_data)
68
+ end
69
+
70
+ def setup_collaborator_object_instance_variables_from_existing(collection:, note_data:, cards_data:)
71
+ @collection = collection
72
+ @note_type = collection.find_note_type_by id: note_data["mid"]
73
+ @field_contents = setup_field_contents_hash_from_existing(note_data: note_data)
74
+ @cards = @note_type.card_templates.map.with_index do |_card_template, index|
75
+ Card.new(note: self, card_data: cards_data[index])
76
+ end
77
+ end
78
+
79
+ def setup_field_contents_hash_from_existing(note_data:)
80
+ field_contents = setup_empty_field_contents_hash
81
+ snake_case_field_names_in_order = note_type.snake_case_field_names
82
+ note_data["flds"].split("\x1F").each_with_index do |fld, ordinal|
83
+ field_contents[snake_case_field_names_in_order[ordinal]] = fld
84
+ end
85
+ field_contents
86
+ end
87
+
88
+ def setup_simple_instance_variables_from_existing(note_data:)
89
+ @id = note_data["id"]
90
+ @guid = note_data["guid"]
91
+ @last_modified_timestamp = note_data["mod"]
92
+ @usn = note_data["usn"]
93
+ @tags = note_data["tags"].split
94
+ @flags = note_data["flags"]
95
+ @data = note_data["data"]
96
+ end
97
+
98
+ def setup_empty_field_contents_hash
99
+ field_contents = {}
100
+ note_type.snake_case_field_names.each { |field_name| field_contents[field_name] = "" }
101
+ field_contents
102
+ end
103
+
104
+ public
105
+
106
+ ##
107
+ # Saves the note to the collection.anki21 database.
108
+ #
109
+ # This also saves the note's cards.
110
+ def save
111
+ collection.find_note_by(id: @id) ? update_note_in_collection_anki21 : insert_new_note_in_collection_anki21
112
+ true
113
+ end
114
+
115
+ private
116
+
117
+ def update_note_in_collection_anki21
118
+ statement = @collection.anki_package.prepare <<~SQL
119
+ update notes set guid = ?, mid = ?, mod = ?, usn = ?, tags = ?,
120
+ flds = ?, sfld = ?, csum = ?, flags = ?, data = ? where id = ?
121
+ SQL
122
+ statement.execute([@guid, note_type.id, @last_modified_timestamp,
123
+ @usn, @tags.join(" "), field_values_separated_by_us, sort_field_value,
124
+ checksum(sort_field_value), @flags, @data, @id])
125
+ cards.each { |card| card.save(note_exists_already: true) }
126
+ end
127
+
128
+ def insert_new_note_in_collection_anki21
129
+ statement = @collection.anki_package.prepare <<~SQL
130
+ insert into notes (id, guid, mid, mod, usn, tags, flds, sfld, csum, flags, data)
131
+ values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
132
+ SQL
133
+ statement.execute([@id, @guid, note_type.id, @last_modified_timestamp,
134
+ @usn, @tags.join(" "), field_values_separated_by_us, sort_field_value,
135
+ checksum(sort_field_value), @flags, @data])
136
+ cards.each(&:save)
137
+ end
138
+
139
+ public
140
+
141
+ ##
142
+ # Overrides BasicObject#method_missing and creates "ghost methods".
143
+ #
144
+ # The ghost methods are the setters and getters for the note field values.
145
+ def method_missing(method_name, field_content = nil)
146
+ raise NoMethodError, "##{method_name} is not defined or a ghost method" unless respond_to_missing? method_name
147
+
148
+ method_name = method_name.to_s
149
+ return @field_contents[method_name] unless method_name.end_with?("=")
150
+
151
+ @field_contents[method_name.chomp("=")] = field_content
152
+ end
153
+
154
+ ##
155
+ # This allows #respond_to? to be accurate for the ghost methods created by #method_missing.
156
+ def respond_to_missing?(method_name, *)
157
+ method_name = method_name.to_s
158
+ if method_name.end_with?("=")
159
+ note_type.snake_case_field_names.include?(method_name.chomp("="))
160
+ else
161
+ note_type.snake_case_field_names.include?(method_name)
162
+ end
163
+ end
164
+
165
+ private
166
+
167
+ def globally_unique_id
168
+ SecureRandom.uuid.slice(5...15)
169
+ end
170
+
171
+ def field_values_separated_by_us
172
+ # The ASCII control code represented by hexadecimal 1F is the Unit Separator (US)
173
+ note_type.snake_case_field_names.map { |field_name| @field_contents[field_name] }.join("\x1F")
174
+ end
175
+
176
+ def sort_field_value
177
+ @field_contents[note_type.snake_case_sort_field_name]
178
+ end
179
+ end
180
+ end
181
+ # 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
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnkiRecord
4
+ ##
5
+ # Module with the NoteField class's attribute readers, writers, and accessors.
6
+ module NoteFieldAttributes
7
+ ##
8
+ # The field's note type.
9
+ attr_reader :note_type
10
+
11
+ ##
12
+ # The field's name.
13
+ attr_accessor :name
14
+
15
+ ##
16
+ # A boolean that indicates if the field is sticky.
17
+ attr_accessor :sticky
18
+
19
+ ##
20
+ # A boolean that indicates if the field is right to left.
21
+ attr_accessor :right_to_left
22
+
23
+ ##
24
+ # The field's font style used when editing.
25
+ attr_accessor :font_style
26
+
27
+ ##
28
+ # The field's font size used when editing.
29
+ attr_accessor :font_size
30
+
31
+ ##
32
+ # The field's description.
33
+ attr_accessor :description
34
+
35
+ ##
36
+ # 0 for the first field of the note type, 1 for the second, etc.
37
+ attr_reader :ordinal_number
38
+ end
39
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnkiRecord
4
+ module NoteFieldDefaults # :nodoc:
5
+ private
6
+
7
+ def default_field_font_style
8
+ "Arial"
9
+ end
10
+
11
+ def default_field_font_size
12
+ 20
13
+ end
14
+
15
+ def default_field_description
16
+ ""
17
+ end
18
+ end
19
+ end