anki_record 0.3.2 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/.rubocop.yml +2 -7
- data/CHANGELOG.md +9 -1
- data/Gemfile +4 -0
- data/Gemfile.lock +9 -2
- data/README.md +79 -132
- data/anki_record.gemspec +2 -2
- data/lib/anki_record/anki21_database/anki21_database.rb +138 -0
- data/lib/anki_record/anki21_database/anki21_database_attributes.rb +31 -0
- data/lib/anki_record/anki21_database/anki21_database_constructors.rb +52 -0
- data/lib/anki_record/anki2_database/anki2_database.rb +44 -0
- data/lib/anki_record/anki_package/anki_package.rb +110 -176
- data/lib/anki_record/card/card.rb +17 -33
- data/lib/anki_record/card/card_attributes.rb +3 -34
- data/lib/anki_record/card_template/card_template.rb +2 -2
- data/lib/anki_record/card_template/card_template_attributes.rb +11 -11
- data/lib/anki_record/collection/collection.rb +20 -154
- data/lib/anki_record/collection/collection_attributes.rb +2 -30
- data/lib/anki_record/deck/deck.rb +11 -10
- data/lib/anki_record/deck/deck_attributes.rb +6 -8
- data/lib/anki_record/deck/deck_defaults.rb +1 -1
- data/lib/anki_record/deck_options_group/deck_options_group.rb +8 -6
- data/lib/anki_record/deck_options_group/deck_options_group_attributes.rb +5 -7
- data/lib/anki_record/helpers/anki_guid_helper.rb +20 -0
- data/lib/anki_record/media/media.rb +36 -0
- data/lib/anki_record/note/note.rb +62 -86
- data/lib/anki_record/note/note_attributes.rb +18 -17
- data/lib/anki_record/note_field/note_field.rb +3 -3
- data/lib/anki_record/note_field/note_field_attributes.rb +9 -9
- data/lib/anki_record/note_type/note_type.rb +13 -14
- data/lib/anki_record/note_type/note_type_attributes.rb +17 -21
- data/lib/anki_record/version.rb +1 -1
- metadata +11 -7
- data/lib/anki_record/helpers/data_query_helper.rb +0 -15
- data/lib/anki_record/note/note_guid_helper.rb +0 -10
@@ -2,236 +2,170 @@
|
|
2
2
|
|
3
3
|
require "pathname"
|
4
4
|
|
5
|
+
require_relative "../anki2_database/anki2_database"
|
6
|
+
require_relative "../anki21_database/anki21_database"
|
7
|
+
require_relative "../media/media"
|
5
8
|
require_relative "../card/card"
|
6
9
|
require_relative "../collection/collection"
|
7
10
|
require_relative "../note/note"
|
8
11
|
require_relative "../database_setup_constants"
|
9
12
|
|
10
|
-
# rubocop:disable Metrics/ClassLength
|
11
13
|
module AnkiRecord
|
12
14
|
##
|
13
|
-
# AnkiPackage represents
|
15
|
+
# AnkiPackage represents the Anki deck package file which has the .apkg file extension
|
16
|
+
#
|
17
|
+
# This is a zip file containing two SQLite databases (collection.anki21 and collection.anki2),
|
18
|
+
# a media file, and possibly the media (images and sound files). The gem currently does not
|
19
|
+
# have any support for adding or changing media in the Anki package.
|
14
20
|
class AnkiPackage
|
15
|
-
|
21
|
+
attr_accessor :anki21_database, :anki2_database, :media, :tmpdir, :tmpfiles, :target_directory, :name # :nodoc:
|
16
22
|
|
17
23
|
##
|
18
|
-
#
|
19
|
-
|
24
|
+
# Creates a new Anki package file (see the README)
|
25
|
+
def self.create(name:, target_directory: Dir.pwd, &closure)
|
26
|
+
anki_package = new
|
27
|
+
anki_package.create_initialize(name:, target_directory:, &closure)
|
28
|
+
anki_package
|
29
|
+
end
|
20
30
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
# See the README for usage details.
|
25
|
-
def initialize(name:, target_directory: Dir.pwd, data: nil, open_path: nil, &closure)
|
26
|
-
check_name_argument_is_valid(name:)
|
27
|
-
@name = name.end_with?(".apkg") ? name[0, name.length - 5] : name
|
31
|
+
def create_initialize(name:, target_directory: Dir.pwd, &closure) # :nodoc:
|
32
|
+
validate_arguments(name:, target_directory:)
|
33
|
+
@name = new_apkg_name(name:)
|
28
34
|
@target_directory = target_directory
|
29
|
-
@
|
30
|
-
|
31
|
-
|
32
|
-
|
35
|
+
@tmpdir = Dir.mktmpdir
|
36
|
+
@tmpfiles = [Anki21Database::FILENAME, Anki2Database::FILENAME, Media::FILENAME]
|
37
|
+
@anki21_database = Anki21Database.create_new(anki_package: self)
|
38
|
+
@anki2_database = Anki2Database.create_new(anki_package: self)
|
39
|
+
@media = Media.create_new(anki_package: self)
|
33
40
|
|
34
|
-
execute_closure_and_zip(
|
41
|
+
execute_closure_and_zip(anki21_database, &closure) if closure
|
35
42
|
end
|
36
43
|
|
37
|
-
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
44
|
+
##
|
45
|
+
# Opens an existing Anki package file to update its contents (see the README)
|
46
|
+
def self.update(path:, &closure)
|
47
|
+
anki_package = new
|
48
|
+
anki_package.update_initialize(path:, &closure)
|
49
|
+
anki_package
|
42
50
|
end
|
43
51
|
|
44
|
-
|
45
|
-
|
46
|
-
def execute_closure_and_zip(collection, &closure)
|
47
|
-
closure.call(collection)
|
48
|
-
rescue StandardError => e
|
49
|
-
destroy_temporary_directory
|
50
|
-
puts_error_and_standard_message(error: e)
|
51
|
-
else
|
52
|
-
zip
|
53
|
-
end
|
54
|
-
|
55
|
-
def setup_other_package_instance_variables
|
56
|
-
@tmpdir = Dir.mktmpdir
|
57
|
-
@tmp_files = []
|
58
|
-
@anki21_database = setup_anki21_database_object
|
59
|
-
@anki2_database = setup_anki2_database_object
|
60
|
-
@media_file = setup_media
|
61
|
-
@collection = Collection.new(anki_package: self)
|
62
|
-
end
|
52
|
+
def update_initialize(path:, &closure) # :nodoc:
|
53
|
+
validate_path(path:)
|
63
54
|
|
64
|
-
|
65
|
-
|
55
|
+
@tmpdir = Dir.mktmpdir
|
56
|
+
unzip_apkg_into_tmpdir(path:)
|
57
|
+
@tmpfiles = [Anki21Database::FILENAME, Anki2Database::FILENAME, Media::FILENAME]
|
58
|
+
@anki21_database = Anki21Database.update_new(anki_package: self)
|
59
|
+
@anki2_database = Anki2Database.update_new(anki_package: self)
|
60
|
+
@media = Media.update_new(anki_package: self)
|
66
61
|
|
67
|
-
|
68
|
-
|
62
|
+
@updating_existing_apkg = true
|
63
|
+
execute_closure_and_zip(anki21_database, &closure) if closure
|
64
|
+
end
|
69
65
|
|
70
|
-
|
71
|
-
|
72
|
-
|
66
|
+
# :nodoc:
|
67
|
+
def zip
|
68
|
+
@updating_existing_apkg ? replace_zip_file : create_zip_file
|
69
|
+
destroy_temporary_directory
|
70
|
+
end
|
73
71
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
db.execute INSERT_COLLECTION_ANKI_21_COL_RECORD
|
80
|
-
db.results_as_hash = true
|
81
|
-
db
|
82
|
-
end
|
72
|
+
# :nocov:
|
73
|
+
def inspect
|
74
|
+
"[= AnkiPackage name: #{name} target_directory: #{target_directory} =]"
|
75
|
+
end
|
76
|
+
# :nocov:
|
83
77
|
|
84
|
-
|
85
|
-
anki2_file_name = "collection.anki2"
|
86
|
-
db = SQLite3::Database.new "#{@tmpdir}/#{anki2_file_name}", options: {}
|
87
|
-
@tmp_files << anki2_file_name
|
88
|
-
db.execute_batch ANKI_SCHEMA_DEFINITION
|
89
|
-
db.execute INSERT_COLLECTION_ANKI_2_COL_RECORD
|
90
|
-
db.close
|
91
|
-
db
|
92
|
-
end
|
78
|
+
private
|
93
79
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
97
|
-
media_file.write("{}")
|
98
|
-
media_file.close
|
99
|
-
@tmp_files << "media"
|
100
|
-
media_file
|
101
|
-
end
|
80
|
+
def validate_path(path:)
|
81
|
+
pathname = Pathname.new(path)
|
82
|
+
raise "*No .apkg file was found at the given path." unless pathname.file? && pathname.extname == ".apkg"
|
102
83
|
|
103
|
-
|
104
|
-
@
|
105
|
-
copy_over_notes_and_cards(note_ids: data[:note_ids])
|
84
|
+
@name = File.basename(pathname.to_s, ".apkg")
|
85
|
+
@target_directory = pathname.expand_path.dirname.to_s
|
106
86
|
end
|
107
87
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
AnkiRecord::Note.new(collection: @collection, data: note_cards_data).save
|
88
|
+
def unzip_apkg_into_tmpdir(path:)
|
89
|
+
Zip::File.open(path) do |zip_file|
|
90
|
+
zip_file.each do |entry|
|
91
|
+
entry.extract("#{tmpdir}/#{entry.name}")
|
113
92
|
end
|
114
93
|
end
|
115
94
|
end
|
116
95
|
|
117
|
-
def
|
118
|
-
|
96
|
+
def validate_arguments(name:, target_directory:)
|
97
|
+
check_name_argument_is_valid(name:)
|
98
|
+
check_target_directory_argument_is_valid(target_directory:)
|
119
99
|
end
|
120
100
|
|
121
|
-
def
|
122
|
-
|
123
|
-
puts "#{error}\n#{standard_error_thrown_in_block_message}"
|
124
|
-
end
|
125
|
-
|
126
|
-
public
|
101
|
+
def check_name_argument_is_valid(name:)
|
102
|
+
return if name.instance_of?(String) && !name.empty? && !name.include?(" ")
|
127
103
|
|
128
|
-
|
129
|
-
# Instantiates a new Anki package object seeded with data from the opened Anki package.
|
130
|
-
#
|
131
|
-
# See the README for details.
|
132
|
-
def self.open(path:, target_directory: nil, &closure)
|
133
|
-
pathname = Pathname.new(path)
|
134
|
-
raise "*No .apkg file was found at the given path." unless pathname.file? && pathname.extname == ".apkg"
|
135
|
-
|
136
|
-
new_apkg_name = "#{File.basename(pathname.to_s, ".apkg")}-#{seconds_since_epoch}"
|
137
|
-
data = col_record_and_note_ids_to_copy_over(pathname: pathname)
|
138
|
-
|
139
|
-
if target_directory
|
140
|
-
new(name: new_apkg_name, data: data, open_path: pathname,
|
141
|
-
target_directory: target_directory, &closure)
|
142
|
-
else
|
143
|
-
new(name: new_apkg_name, data: data, open_path: pathname, &closure)
|
104
|
+
raise ArgumentError, "The package name must be a string without spaces."
|
144
105
|
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def was_instantiated_from_existing_apkg? # :nodoc:
|
148
|
-
!@open_path.nil?
|
149
|
-
end
|
150
|
-
|
151
|
-
# rubocop:disable Metrics/MethodLength
|
152
|
-
# :nodoc:
|
153
|
-
def temporarily_unzip_source_apkg
|
154
|
-
raise ArgumentError unless @open_path && block_given?
|
155
106
|
|
156
|
-
|
157
|
-
|
158
|
-
next unless entry.name == "collection.anki21"
|
107
|
+
def check_target_directory_argument_is_valid(target_directory:)
|
108
|
+
return if File.directory?(target_directory)
|
159
109
|
|
160
|
-
|
161
|
-
source_collection_anki21 = SQLite3::Database.open "collection.anki21"
|
162
|
-
source_collection_anki21.results_as_hash = true
|
163
|
-
|
164
|
-
yield source_collection_anki21
|
165
|
-
end
|
110
|
+
raise ArgumentError, "No directory was found at the given path."
|
166
111
|
end
|
167
|
-
File.delete("collection.anki21")
|
168
|
-
end
|
169
|
-
# rubocop:enable Metrics/MethodLength
|
170
112
|
|
171
|
-
|
172
|
-
|
113
|
+
def new_apkg_name(name:)
|
114
|
+
name.end_with?(".apkg") ? name[0, name.length - 5] : name
|
115
|
+
end
|
173
116
|
|
174
117
|
# rubocop:disable Metrics/MethodLength
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
data = { col_record: col_record, note_ids: note_ids }
|
188
|
-
end
|
189
|
-
end
|
190
|
-
File.delete("collection.anki21")
|
191
|
-
data
|
118
|
+
def execute_closure_and_zip(anki21_database)
|
119
|
+
yield(anki21_database)
|
120
|
+
rescue StandardError => e
|
121
|
+
destroy_temporary_directory
|
122
|
+
puts e.backtrace.reverse
|
123
|
+
puts e
|
124
|
+
puts "An error occurred within the block argument."
|
125
|
+
puts "The temporary files have been deleted."
|
126
|
+
puts "If you were creating a new Anki package, nothing was saved."
|
127
|
+
puts "If you were updating an existing one, it was not changed."
|
128
|
+
else
|
129
|
+
zip
|
192
130
|
end
|
193
|
-
# rubocop:enable Metrics/AbcSize
|
194
131
|
# rubocop:enable Metrics/MethodLength
|
195
|
-
end
|
196
|
-
|
197
|
-
##
|
198
|
-
# Zips the temporary files (collection.anki21, collection.anki2, and media) into a new *.apkg package file.
|
199
|
-
#
|
200
|
-
# The temporary files, and the temporary directory they were in, are deleted after zipping.
|
201
|
-
def zip
|
202
|
-
create_zip_file && destroy_temporary_directory
|
203
|
-
end
|
204
132
|
|
205
|
-
|
133
|
+
def destroy_temporary_directory
|
134
|
+
FileUtils.rm_rf(tmpdir)
|
135
|
+
end
|
206
136
|
|
207
137
|
def create_zip_file
|
208
138
|
Zip::File.open(target_zip_file, create: true) do |zip_file|
|
209
|
-
|
210
|
-
zip_file.add(file_name, File.join(
|
139
|
+
tmpfiles.each do |file_name|
|
140
|
+
zip_file.add(file_name, File.join(tmpdir, file_name))
|
211
141
|
end
|
212
142
|
end
|
213
143
|
true
|
214
144
|
end
|
215
145
|
|
216
|
-
|
217
|
-
|
146
|
+
# rubocop:disable Metrics/MethodLength
|
147
|
+
def replace_zip_file
|
148
|
+
File.rename(target_zip_file, tmp_original_zip_file)
|
149
|
+
begin
|
150
|
+
create_zip_file
|
151
|
+
FileUtils.rm(tmp_original_zip_file)
|
152
|
+
rescue StandardError => e
|
153
|
+
puts e.backtrace.reverse
|
154
|
+
puts e
|
155
|
+
puts "An error occurred during zipping the new version of the Anki package."
|
156
|
+
puts "The original package has not been changed"
|
157
|
+
File.rename(tmp_original_zip_file, target_zip_file)
|
158
|
+
end
|
159
|
+
true
|
218
160
|
end
|
161
|
+
# rubocop:enable Metrics/MethodLength
|
219
162
|
|
220
|
-
def
|
221
|
-
|
163
|
+
def target_zip_file
|
164
|
+
"#{target_directory}/#{name}.apkg"
|
222
165
|
end
|
223
166
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
def open?
|
228
|
-
!closed?
|
229
|
-
end
|
230
|
-
|
231
|
-
# :nodoc:
|
232
|
-
def closed?
|
233
|
-
@anki21_database.closed?
|
234
|
-
end
|
167
|
+
def tmp_original_zip_file
|
168
|
+
"#{target_zip_file}-old"
|
169
|
+
end
|
235
170
|
end
|
236
171
|
end
|
237
|
-
# rubocop:enable Metrics/ClassLength
|
@@ -6,39 +6,38 @@ require_relative "card_attributes"
|
|
6
6
|
|
7
7
|
module AnkiRecord
|
8
8
|
##
|
9
|
-
# Card represents an Anki card.
|
9
|
+
# Card represents an Anki card. The cards are indirectly created when creating notes.
|
10
10
|
class Card
|
11
11
|
include CardAttributes
|
12
12
|
include Helpers::TimeHelper
|
13
13
|
include Helpers::SharedConstantsHelper
|
14
14
|
|
15
|
-
|
15
|
+
# :nodoc:
|
16
|
+
|
17
|
+
def initialize(note:, card_template: nil, card_data: nil)
|
16
18
|
@note = note
|
17
19
|
if card_template
|
18
|
-
setup_instance_variables_for_new_card(card_template:
|
20
|
+
setup_instance_variables_for_new_card(card_template:)
|
19
21
|
elsif card_data
|
20
|
-
setup_instance_variables_from_existing(card_data:
|
22
|
+
setup_instance_variables_from_existing(card_data:)
|
21
23
|
else
|
22
24
|
raise ArgumentError
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
28
|
+
def save(note_exists_already: false)
|
29
|
+
note_exists_already ? update_card_in_collection_anki21 : insert_new_card_in_collection_anki21
|
30
|
+
end
|
31
|
+
|
26
32
|
private
|
27
33
|
|
34
|
+
# rubocop:disable Metrics/MethodLength
|
28
35
|
def setup_instance_variables_for_new_card(card_template:)
|
29
36
|
raise ArgumentError unless @note.note_type == card_template.note_type
|
30
37
|
|
31
|
-
setup_collaborator_object_instance_variables_for_new_card(card_template: card_template)
|
32
|
-
setup_simple_instance_variables_for_new_card
|
33
|
-
end
|
34
|
-
|
35
|
-
def setup_collaborator_object_instance_variables_for_new_card(card_template:)
|
36
38
|
@card_template = card_template
|
37
39
|
@deck = @note.deck
|
38
|
-
@
|
39
|
-
end
|
40
|
-
|
41
|
-
def setup_simple_instance_variables_for_new_card
|
40
|
+
@anki21_database = @deck.anki21_database
|
42
41
|
@id = milliseconds_since_epoch
|
43
42
|
@last_modified_timestamp = seconds_since_epoch
|
44
43
|
@usn = NEW_OBJECT_USN
|
@@ -47,37 +46,22 @@ module AnkiRecord
|
|
47
46
|
end
|
48
47
|
@data = "{}"
|
49
48
|
end
|
49
|
+
# rubocop:enable Metrics/MethodLength
|
50
50
|
|
51
51
|
def setup_instance_variables_from_existing(card_data:)
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
def setup_collaborator_object_instance_variables_from_existing(card_data:)
|
57
|
-
@collection = note.note_type.collection
|
58
|
-
@deck = collection.find_deck_by id: card_data["did"]
|
52
|
+
@anki21_database = note.anki21_database
|
53
|
+
@deck = anki21_database.find_deck_by id: card_data["did"]
|
59
54
|
@card_template = note.note_type.card_templates.find do |card_template|
|
60
55
|
card_template.ordinal_number == card_data["ord"]
|
61
56
|
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def setup_simple_instance_variables_from_existing(card_data:)
|
65
57
|
@last_modified_timestamp = card_data["mod"]
|
66
58
|
%w[id usn type queue due ivl factor reps lapses left odue odid flags data].each do |instance_variable_name|
|
67
59
|
instance_variable_set "@#{instance_variable_name}", card_data[instance_variable_name]
|
68
60
|
end
|
69
61
|
end
|
70
62
|
|
71
|
-
public
|
72
|
-
|
73
|
-
def save(note_exists_already: false) # :nodoc:
|
74
|
-
note_exists_already ? update_card_in_collection_anki21 : insert_new_card_in_collection_anki21
|
75
|
-
end
|
76
|
-
|
77
|
-
private
|
78
|
-
|
79
63
|
def update_card_in_collection_anki21
|
80
|
-
statement =
|
64
|
+
statement = anki21_database.prepare <<~SQL
|
81
65
|
update cards set nid = ?, did = ?, ord = ?, mod = ?, usn = ?, type = ?,
|
82
66
|
queue = ?, due = ?, ivl = ?, factor = ?, reps = ?, lapses = ?,
|
83
67
|
left = ?, odue = ?, odid = ?, flags = ?, data = ? where id = ?
|
@@ -89,7 +73,7 @@ module AnkiRecord
|
|
89
73
|
end
|
90
74
|
|
91
75
|
def insert_new_card_in_collection_anki21
|
92
|
-
statement =
|
76
|
+
statement = anki21_database.prepare <<~SQL
|
93
77
|
insert into cards (id, nid, did, ord,
|
94
78
|
mod, usn, type, queue,
|
95
79
|
due, ivl, factor, reps,
|
@@ -1,39 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AnkiRecord
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
##
|
8
|
-
# The card's note object.
|
9
|
-
attr_reader :note
|
10
|
-
|
11
|
-
##
|
12
|
-
# The card's deck object.
|
13
|
-
attr_reader :deck
|
14
|
-
|
15
|
-
##
|
16
|
-
# The card's collection object.
|
17
|
-
attr_reader :collection
|
18
|
-
|
19
|
-
##
|
20
|
-
# The card's card template object.
|
21
|
-
attr_reader :card_template
|
22
|
-
|
23
|
-
##
|
24
|
-
# The card's id.
|
25
|
-
#
|
26
|
-
# This is also the number of milliseconds since the 1970 epoch at which the card was created.
|
27
|
-
attr_reader :id
|
28
|
-
|
29
|
-
##
|
30
|
-
# The number of seconds since the 1970 epoch at which the card was last modified.
|
31
|
-
attr_reader :last_modified_timestamp
|
32
|
-
|
33
|
-
##
|
34
|
-
# The card's update sequence number.
|
35
|
-
attr_reader :usn
|
36
|
-
|
37
|
-
attr_reader :type, :queue, :due, :ivl, :factor, :reps, :lapses, :left, :odue, :odid, :flags, :data
|
4
|
+
module CardAttributes # :nodoc:
|
5
|
+
attr_reader :anki21_database, :note, :deck, :card_template, :id, :last_modified_timestamp, :usn, :type, :queue,
|
6
|
+
:due, :ivl, :factor, :reps, :lapses, :left, :odue, :odid, :flags, :data
|
38
7
|
end
|
39
8
|
end
|
@@ -14,9 +14,9 @@ module AnkiRecord
|
|
14
14
|
|
15
15
|
@note_type = note_type
|
16
16
|
if args
|
17
|
-
setup_card_template_instance_variables_from_existing(args:
|
17
|
+
setup_card_template_instance_variables_from_existing(args:)
|
18
18
|
else
|
19
|
-
setup_card_template_instance_variables(name:
|
19
|
+
setup_card_template_instance_variables(name:)
|
20
20
|
end
|
21
21
|
|
22
22
|
@note_type.add_card_template self
|
@@ -5,25 +5,25 @@ module AnkiRecord
|
|
5
5
|
# Module with the CardTemplate class's attribute readers, writers, and accessors.
|
6
6
|
module CardTemplateAttributes
|
7
7
|
##
|
8
|
-
# The card template's name
|
8
|
+
# The card template's name
|
9
9
|
attr_accessor :name
|
10
10
|
|
11
11
|
##
|
12
|
-
# The card template's font style in the browser
|
12
|
+
# The card template's font style in the browser
|
13
13
|
attr_accessor :browser_font_style
|
14
14
|
|
15
15
|
##
|
16
|
-
# The card template's font size used in the browser
|
16
|
+
# The card template's font size used in the browser
|
17
17
|
attr_accessor :browser_font_size
|
18
18
|
|
19
19
|
##
|
20
|
-
# The card template's question format
|
20
|
+
# The card template's question format
|
21
21
|
attr_reader :question_format
|
22
22
|
|
23
23
|
##
|
24
|
-
# Sets the question format of the card template
|
24
|
+
# Sets the question format of the card template
|
25
25
|
#
|
26
|
-
# Raises an ArgumentError if the specified format attempts to use invalid fields
|
26
|
+
# Raises an ArgumentError if the specified format attempts to use invalid fields
|
27
27
|
def question_format=(format)
|
28
28
|
fields_in_specified_format = format.scan(/{{.+?}}/).map do |capture|
|
29
29
|
capture.chomp("}}").reverse.chomp("{{").reverse
|
@@ -38,13 +38,13 @@ module AnkiRecord
|
|
38
38
|
end
|
39
39
|
|
40
40
|
##
|
41
|
-
# The card template's answer format
|
41
|
+
# The card template's answer format
|
42
42
|
attr_reader :answer_format
|
43
43
|
|
44
44
|
##
|
45
|
-
# Sets the answer format of the card template
|
45
|
+
# Sets the answer format of the card template
|
46
46
|
#
|
47
|
-
# Raises an ArgumentError if the specified format attempts to use invalid fields
|
47
|
+
# Raises an ArgumentError if the specified format attempts to use invalid fields
|
48
48
|
def answer_format=(format)
|
49
49
|
fields_in_specified_format = format.scan(/{{.+?}}/).map do |capture|
|
50
50
|
capture.chomp("}}").reverse.chomp("{{").reverse
|
@@ -59,11 +59,11 @@ module AnkiRecord
|
|
59
59
|
end
|
60
60
|
|
61
61
|
##
|
62
|
-
# The card template's note type object
|
62
|
+
# The card template's note type object
|
63
63
|
attr_reader :note_type
|
64
64
|
|
65
65
|
##
|
66
|
-
# 0 for the first card template of the note type, 1 for the second, etc
|
66
|
+
# 0 for the first card template of the note type, 1 for the second, etc
|
67
67
|
attr_reader :ordinal_number
|
68
68
|
end
|
69
69
|
end
|