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.
- checksums.yaml +4 -4
- data/.rubocop.yml +13 -2
- data/CHANGELOG.md +37 -9
- data/Gemfile +3 -1
- data/Gemfile.lock +10 -2
- data/README.md +120 -35
- data/anki_record.gemspec +1 -5
- data/lib/anki_record/anki_package/anki_package.rb +237 -0
- data/lib/anki_record/card/card.rb +108 -0
- data/lib/anki_record/card/card_attributes.rb +39 -0
- data/lib/anki_record/card_template/card_template.rb +64 -0
- data/lib/anki_record/card_template/card_template_attributes.rb +69 -0
- data/lib/anki_record/collection/collection.rb +182 -0
- data/lib/anki_record/collection/collection_attributes.rb +35 -0
- data/lib/anki_record/database_setup_constants.rb +88 -0
- data/lib/anki_record/deck/deck.rb +99 -0
- data/lib/anki_record/deck/deck_attributes.rb +30 -0
- data/lib/anki_record/deck/deck_defaults.rb +19 -0
- data/lib/anki_record/{deck_options_group.rb → deck_options_group/deck_options_group.rb} +12 -31
- data/lib/anki_record/deck_options_group/deck_options_group_attributes.rb +23 -0
- data/lib/anki_record/helpers/checksum_helper.rb +10 -11
- data/lib/anki_record/helpers/data_query_helper.rb +15 -0
- data/lib/anki_record/helpers/shared_constants_helper.rb +6 -6
- data/lib/anki_record/helpers/time_helper.rb +18 -13
- data/lib/anki_record/note/note.rb +178 -0
- data/lib/anki_record/note/note_attributes.rb +56 -0
- data/lib/anki_record/note_field/note_field.rb +62 -0
- data/lib/anki_record/note_field/note_field_attributes.rb +39 -0
- data/lib/anki_record/note_field/note_field_defaults.rb +19 -0
- data/lib/anki_record/note_type/note_type.rb +161 -0
- data/lib/anki_record/note_type/note_type_attributes.rb +80 -0
- data/lib/anki_record/note_type/note_type_defaults.rb +38 -0
- data/lib/anki_record/version.rb +1 -1
- data/lib/anki_record.rb +1 -16
- metadata +26 -16
- data/lib/anki_record/anki_package.rb +0 -194
- data/lib/anki_record/card.rb +0 -75
- data/lib/anki_record/card_template.rb +0 -105
- data/lib/anki_record/collection.rb +0 -105
- data/lib/anki_record/db/anki_schema_definition.rb +0 -77
- data/lib/anki_record/db/clean_collection21_record.rb +0 -10
- data/lib/anki_record/db/clean_collection2_record.rb +0 -10
- data/lib/anki_record/deck.rb +0 -101
- data/lib/anki_record/note.rb +0 -135
- data/lib/anki_record/note_field.rb +0 -84
- data/lib/anki_record/note_type.rb +0 -233
@@ -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
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../card_template/card_template"
|
4
|
+
require_relative "../helpers/shared_constants_helper"
|
5
|
+
require_relative "../helpers/time_helper"
|
6
|
+
require_relative "../note_field/note_field"
|
7
|
+
require_relative "note_type_attributes"
|
8
|
+
require_relative "note_type_defaults"
|
9
|
+
|
10
|
+
module AnkiRecord
|
11
|
+
##
|
12
|
+
# NoteType represents an Anki note type (also called a model).
|
13
|
+
#
|
14
|
+
# The attributes are documented in the NoteTypeAttributes module.
|
15
|
+
class NoteType
|
16
|
+
include Helpers::SharedConstantsHelper
|
17
|
+
include Helpers::TimeHelper
|
18
|
+
include NoteTypeAttributes
|
19
|
+
include NoteTypeDefaults
|
20
|
+
|
21
|
+
NOTE_TYPES_WITHOUT_TAGS_AND_VERS_VALUES = ["Basic", "Basic (and reversed card)",
|
22
|
+
"Basic (optional reversed card)", "Basic (type in the answer)"].freeze
|
23
|
+
|
24
|
+
def initialize(collection:, name: nil, args: nil)
|
25
|
+
raise ArgumentError unless (name && args.nil?) || (args && args["name"])
|
26
|
+
|
27
|
+
@collection = collection
|
28
|
+
|
29
|
+
if args
|
30
|
+
setup_note_type_instance_variables_from_existing(args: args)
|
31
|
+
else
|
32
|
+
setup_instance_variables_for_new_note_type(name: name)
|
33
|
+
end
|
34
|
+
|
35
|
+
@collection.add_note_type self
|
36
|
+
save
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Saves the note type to the collection.anki21 database
|
41
|
+
def save
|
42
|
+
collection_models_hash = collection.models_json
|
43
|
+
collection_models_hash[@id] = to_h
|
44
|
+
sql = "update col set models = ? where id = ?"
|
45
|
+
collection.anki_package.prepare(sql).execute([JSON.generate(collection_models_hash), collection.id])
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_h # :nodoc:
|
49
|
+
self_to_h = { id: @id, name: @name, type: @cloze ? 1 : 0,
|
50
|
+
mod: @last_modified_timestamp, usn: @usn, sortf: @sort_field, did: @deck_id,
|
51
|
+
tmpls: @card_templates.map(&:to_h), flds: @note_fields.map(&:to_h), css: @css,
|
52
|
+
latexPre: @latex_preamble, latexPost: @latex_postamble, latexsvg: @latex_svg,
|
53
|
+
req: @req }
|
54
|
+
self_to_h.merge({ tags: @tags, vers: @vers }) unless NOTE_TYPES_WITHOUT_TAGS_AND_VERS_VALUES.include?(@name)
|
55
|
+
self_to_h
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Returns the note type object's card template with name +name+ or nil if it is not found
|
60
|
+
def find_card_template_by(name:)
|
61
|
+
card_templates.find { |template| template.name == name }
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Returns an array of the note type's fields' names ordered by field ordinal values.
|
66
|
+
def field_names_in_order
|
67
|
+
@note_fields.sort_by(&:ordinal_number).map(&:name)
|
68
|
+
end
|
69
|
+
|
70
|
+
def snake_case_field_names # :nodoc:
|
71
|
+
field_names_in_order.map { |field_name| field_name.downcase.gsub(" ", "_") }
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Returns the name of the note type's field used to sort notes in the browser.
|
76
|
+
def sort_field_name
|
77
|
+
@note_fields.find { |field| field.ordinal_number == @sort_field }&.name
|
78
|
+
end
|
79
|
+
|
80
|
+
def snake_case_sort_field_name # :nodoc:
|
81
|
+
sort_field_name.downcase.gsub(" ", "_")
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Returns allowed field_name values in {{field_name}} in the question format.
|
86
|
+
def allowed_card_template_question_format_field_names
|
87
|
+
allowed = field_names_in_order
|
88
|
+
cloze ? allowed + field_names_in_order.map { |field_name| "cloze:#{field_name}" } : allowed
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Returns allowed field_name values in {{field_name}} in the answer format.
|
93
|
+
def allowed_card_template_answer_format_field_names
|
94
|
+
allowed_card_template_question_format_field_names + ["FrontSide"]
|
95
|
+
end
|
96
|
+
|
97
|
+
def add_note_field(note_field) # :nodoc:
|
98
|
+
raise ArgumentError unless note_field.instance_of?(AnkiRecord::NoteField)
|
99
|
+
|
100
|
+
@note_fields << note_field
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_card_template(card_template) # :nodoc:
|
104
|
+
raise ArgumentError unless card_template.instance_of?(AnkiRecord::CardTemplate)
|
105
|
+
|
106
|
+
@card_templates << card_template
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def setup_note_type_instance_variables_from_existing(args:)
|
112
|
+
setup_collaborator_object_instance_variables_from_existing(args: args)
|
113
|
+
setup_simple_instance_variables_from_existing(args: args)
|
114
|
+
end
|
115
|
+
|
116
|
+
def setup_collaborator_object_instance_variables_from_existing(args:)
|
117
|
+
@note_fields = []
|
118
|
+
args["flds"].each { |fld| NoteField.new(note_type: self, args: fld) }
|
119
|
+
@card_templates = []
|
120
|
+
args["tmpls"].each { |tmpl| CardTemplate.new(note_type: self, args: tmpl) }
|
121
|
+
end
|
122
|
+
|
123
|
+
def setup_simple_instance_variables_from_existing(args:)
|
124
|
+
%w[id name usn css req tags vers].each do |note_type_attribute|
|
125
|
+
instance_variable_set "@#{note_type_attribute}", args[note_type_attribute]
|
126
|
+
end
|
127
|
+
@cloze = args["type"] == 1
|
128
|
+
@last_modified_timestamp = args["mod"]
|
129
|
+
@sort_field = args["sortf"]
|
130
|
+
@deck_id = args["did"]
|
131
|
+
@latex_preamble = args["latexPre"]
|
132
|
+
@latex_postamble = args["latexPost"]
|
133
|
+
@latex_svg = args["latexsvg"]
|
134
|
+
end
|
135
|
+
|
136
|
+
def setup_instance_variables_for_new_note_type(name:)
|
137
|
+
@name = name
|
138
|
+
@cloze = false
|
139
|
+
setup_collaborator_object_instance_variables_for_new_note_type
|
140
|
+
setup_simple_instance_variables_for_new_note_type
|
141
|
+
end
|
142
|
+
|
143
|
+
def setup_collaborator_object_instance_variables_for_new_note_type
|
144
|
+
@note_fields = []
|
145
|
+
@card_templates = []
|
146
|
+
end
|
147
|
+
|
148
|
+
def setup_simple_instance_variables_for_new_note_type
|
149
|
+
@id = milliseconds_since_epoch
|
150
|
+
@last_modified_timestamp = seconds_since_epoch
|
151
|
+
@usn = NEW_OBJECT_USN
|
152
|
+
@sort_field = default_note_type_sort_field
|
153
|
+
@deck_id = @tags = @vers = nil
|
154
|
+
@css = default_css
|
155
|
+
@latex_preamble = default_latex_preamble
|
156
|
+
@latex_postamble = default_latex_postamble
|
157
|
+
@latex_svg = false
|
158
|
+
@req = []
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnkiRecord
|
4
|
+
##
|
5
|
+
# Module with the NoteType class's attribute readers, writers, and accessors.
|
6
|
+
module NoteTypeAttributes
|
7
|
+
##
|
8
|
+
# The note type's collection object.
|
9
|
+
attr_reader :collection
|
10
|
+
|
11
|
+
##
|
12
|
+
# The note type's id.
|
13
|
+
attr_reader :id
|
14
|
+
|
15
|
+
##
|
16
|
+
# The note type's name.
|
17
|
+
attr_accessor :name
|
18
|
+
|
19
|
+
##
|
20
|
+
# A boolean that indicates if this note type is a cloze-deletion note type.
|
21
|
+
attr_accessor :cloze
|
22
|
+
|
23
|
+
##
|
24
|
+
# The number of seconds since the 1970 epoch at which the note type was last modified.
|
25
|
+
attr_reader :last_modified_timestamp
|
26
|
+
|
27
|
+
##
|
28
|
+
# The note type's update sequence number.
|
29
|
+
attr_reader :usn
|
30
|
+
|
31
|
+
##
|
32
|
+
# The note type's sort field.
|
33
|
+
attr_reader :sort_field
|
34
|
+
|
35
|
+
##
|
36
|
+
# The note type's CSS.
|
37
|
+
attr_accessor :css
|
38
|
+
|
39
|
+
##
|
40
|
+
# The note type's LaTeX preamble.
|
41
|
+
attr_reader :latex_preamble
|
42
|
+
|
43
|
+
##
|
44
|
+
# The note type's LaTeX postamble.
|
45
|
+
attr_reader :latex_postamble
|
46
|
+
|
47
|
+
##
|
48
|
+
# The note type's card template objects, as an array.
|
49
|
+
attr_reader :card_templates
|
50
|
+
|
51
|
+
##
|
52
|
+
# The note type's field objects, as an array.
|
53
|
+
attr_reader :note_fields
|
54
|
+
|
55
|
+
##
|
56
|
+
# The note type's deck's id.
|
57
|
+
attr_reader :deck_id
|
58
|
+
|
59
|
+
##
|
60
|
+
# The note type's deck.
|
61
|
+
def deck
|
62
|
+
return nil unless @deck_id
|
63
|
+
|
64
|
+
@collection.find_deck_by id: @deck_id
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Sets the note type's deck object.
|
69
|
+
def deck=(deck)
|
70
|
+
unless deck.instance_of?(AnkiRecord::Deck)
|
71
|
+
raise ArgumentError,
|
72
|
+
"You can only set this attribute to an instance of AnkiRecord::Deck."
|
73
|
+
end
|
74
|
+
|
75
|
+
@deck_id = deck.id
|
76
|
+
end
|
77
|
+
|
78
|
+
attr_reader :latex_svg, :tags, :req, :vers
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnkiRecord
|
4
|
+
module NoteTypeDefaults # :nodoc:
|
5
|
+
private
|
6
|
+
|
7
|
+
def default_note_type_sort_field
|
8
|
+
0
|
9
|
+
end
|
10
|
+
|
11
|
+
def default_css
|
12
|
+
<<~CSS
|
13
|
+
.card {
|
14
|
+
color: black;
|
15
|
+
background-color: transparent;
|
16
|
+
text-align: center;
|
17
|
+
}
|
18
|
+
CSS
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_latex_preamble
|
22
|
+
<<~LATEX_PRE
|
23
|
+
\\documentclass[12pt]{article}
|
24
|
+
\\special{papersize=3in,5in}
|
25
|
+
\\usepackage{amssymb,amsmath}
|
26
|
+
\\pagestyle{empty}
|
27
|
+
\\setlength{\\parindent}{0in}
|
28
|
+
\\begin{document}
|
29
|
+
LATEX_PRE
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_latex_postamble
|
33
|
+
<<~LATEX_POST
|
34
|
+
\\end{document}
|
35
|
+
LATEX_POST
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/anki_record/version.rb
CHANGED
data/lib/anki_record.rb
CHANGED
@@ -3,24 +3,9 @@
|
|
3
3
|
require "sqlite3"
|
4
4
|
require "zip"
|
5
5
|
|
6
|
-
require_relative "anki_record/anki_package"
|
6
|
+
require_relative "anki_record/anki_package/anki_package"
|
7
7
|
require_relative "anki_record/version"
|
8
8
|
|
9
|
-
##
|
10
|
-
# This module is the namespace for all AnkiRecord classes:
|
11
|
-
# - AnkiPackage
|
12
|
-
# - Card
|
13
|
-
# - CardTemplate
|
14
|
-
# - Collection
|
15
|
-
# - DeckOptionsGroup
|
16
|
-
# - Deck
|
17
|
-
# - Note
|
18
|
-
# - NoteField
|
19
|
-
# - NoteType
|
20
|
-
#
|
21
|
-
# And modules:
|
22
|
-
# - SharedConstantsHelper
|
23
|
-
# - TimeHelper
|
24
9
|
module AnkiRecord
|
25
10
|
class Error < StandardError; end # :nodoc:
|
26
11
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: anki_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kyle Rego
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubyzip
|
@@ -38,8 +38,8 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.3'
|
41
|
-
description: "
|
42
|
-
|
41
|
+
description: " A Ruby library which provides a programmatic interface to Anki flashcard
|
42
|
+
decks (.apkg files/zipped Anki SQLite databases).\n"
|
43
43
|
email:
|
44
44
|
- regoky@outlook.com
|
45
45
|
executables: []
|
@@ -57,21 +57,31 @@ files:
|
|
57
57
|
- Rakefile
|
58
58
|
- anki_record.gemspec
|
59
59
|
- lib/anki_record.rb
|
60
|
-
- lib/anki_record/anki_package.rb
|
61
|
-
- lib/anki_record/card.rb
|
62
|
-
- lib/anki_record/
|
63
|
-
- lib/anki_record/
|
64
|
-
- lib/anki_record/
|
65
|
-
- lib/anki_record/
|
66
|
-
- lib/anki_record/
|
67
|
-
- lib/anki_record/
|
68
|
-
- lib/anki_record/
|
60
|
+
- lib/anki_record/anki_package/anki_package.rb
|
61
|
+
- lib/anki_record/card/card.rb
|
62
|
+
- lib/anki_record/card/card_attributes.rb
|
63
|
+
- lib/anki_record/card_template/card_template.rb
|
64
|
+
- lib/anki_record/card_template/card_template_attributes.rb
|
65
|
+
- lib/anki_record/collection/collection.rb
|
66
|
+
- lib/anki_record/collection/collection_attributes.rb
|
67
|
+
- lib/anki_record/database_setup_constants.rb
|
68
|
+
- lib/anki_record/deck/deck.rb
|
69
|
+
- lib/anki_record/deck/deck_attributes.rb
|
70
|
+
- lib/anki_record/deck/deck_defaults.rb
|
71
|
+
- lib/anki_record/deck_options_group/deck_options_group.rb
|
72
|
+
- lib/anki_record/deck_options_group/deck_options_group_attributes.rb
|
69
73
|
- lib/anki_record/helpers/checksum_helper.rb
|
74
|
+
- lib/anki_record/helpers/data_query_helper.rb
|
70
75
|
- lib/anki_record/helpers/shared_constants_helper.rb
|
71
76
|
- lib/anki_record/helpers/time_helper.rb
|
72
|
-
- lib/anki_record/note.rb
|
73
|
-
- lib/anki_record/
|
74
|
-
- lib/anki_record/
|
77
|
+
- lib/anki_record/note/note.rb
|
78
|
+
- lib/anki_record/note/note_attributes.rb
|
79
|
+
- lib/anki_record/note_field/note_field.rb
|
80
|
+
- lib/anki_record/note_field/note_field_attributes.rb
|
81
|
+
- lib/anki_record/note_field/note_field_defaults.rb
|
82
|
+
- lib/anki_record/note_type/note_type.rb
|
83
|
+
- lib/anki_record/note_type/note_type_attributes.rb
|
84
|
+
- lib/anki_record/note_type/note_type_defaults.rb
|
75
85
|
- lib/anki_record/version.rb
|
76
86
|
homepage: https://github.com/KyleRego/anki_record
|
77
87
|
licenses:
|
@@ -1,194 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "pry"
|
4
|
-
require "pathname"
|
5
|
-
|
6
|
-
require_relative "card"
|
7
|
-
require_relative "note"
|
8
|
-
|
9
|
-
require_relative "db/anki_schema_definition"
|
10
|
-
require_relative "db/clean_collection2_record"
|
11
|
-
require_relative "db/clean_collection21_record"
|
12
|
-
require_relative "collection"
|
13
|
-
|
14
|
-
module AnkiRecord
|
15
|
-
##
|
16
|
-
# Represents an Anki SQLite3 package/database
|
17
|
-
#
|
18
|
-
# Use ::new to create a new object or ::open to create an object from an existing one
|
19
|
-
class AnkiPackage
|
20
|
-
NAME_ERROR_MESSAGE = "The name argument must be a string without spaces."
|
21
|
-
PATH_ERROR_MESSAGE = "*No .apkg file was found at the given path."
|
22
|
-
STANDARD_ERROR_MESSAGE = <<-MSG
|
23
|
-
An error occurred.
|
24
|
-
The temporary *.anki21 database has been deleted.
|
25
|
-
No *.apkg zip file has been saved.
|
26
|
-
MSG
|
27
|
-
|
28
|
-
private_constant :NAME_ERROR_MESSAGE, :PATH_ERROR_MESSAGE, :STANDARD_ERROR_MESSAGE
|
29
|
-
|
30
|
-
##
|
31
|
-
# The collection object of the package
|
32
|
-
attr_reader :collection
|
33
|
-
|
34
|
-
##
|
35
|
-
# Creates a new object which represents an Anki SQLite3 database
|
36
|
-
#
|
37
|
-
# This method takes an optional block argument.
|
38
|
-
#
|
39
|
-
# When a block argument is used, execution is yielded to the block.
|
40
|
-
# After the block executes, the temporary files are zipped into the +name+.apkg file
|
41
|
-
# which is saved in +directory+. +directory+ is the current working directory by default.
|
42
|
-
# If the block throws a runtime error, the temporary files are deleted but the zip file is not created.
|
43
|
-
#
|
44
|
-
# When no block argument is used, #zip must be called explicitly at the end of your script.
|
45
|
-
def initialize(name:, directory: Dir.pwd, &closure)
|
46
|
-
setup_package_instance_variables(name: name, directory: directory)
|
47
|
-
|
48
|
-
execute_closure_and_zip(self, &closure) if block_given?
|
49
|
-
end
|
50
|
-
|
51
|
-
##
|
52
|
-
# Executes a raw SQL statement against the *.anki21 database
|
53
|
-
#
|
54
|
-
# Do not use this to execute data definition language SQL statements
|
55
|
-
# (i.e. do not create, alter, or drop tables or indexes)
|
56
|
-
# unless you have a good reason to change the database schema.
|
57
|
-
def execute(raw_sql_string)
|
58
|
-
@anki21_database.execute raw_sql_string
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
63
|
-
def execute_closure_and_zip(object_to_yield, &closure)
|
64
|
-
closure.call(object_to_yield)
|
65
|
-
rescue StandardError => e
|
66
|
-
destroy_temporary_directory
|
67
|
-
puts_error_and_standard_message(error: e)
|
68
|
-
else
|
69
|
-
zip
|
70
|
-
end
|
71
|
-
|
72
|
-
def setup_package_instance_variables(name:, directory:)
|
73
|
-
@name = check_name_is_valid(name: name)
|
74
|
-
@directory = directory # TODO: check directory is valid
|
75
|
-
@tmpdir = Dir.mktmpdir
|
76
|
-
@tmp_files = []
|
77
|
-
@anki21_database = setup_anki21_database_object
|
78
|
-
@anki2_database = setup_anki2_database_object
|
79
|
-
@media_file = setup_media
|
80
|
-
@collection = Collection.new(anki_package: self)
|
81
|
-
end
|
82
|
-
|
83
|
-
def check_name_is_valid(name:)
|
84
|
-
raise ArgumentError, NAME_ERROR_MESSAGE unless name.instance_of?(String) && !name.empty? && !name.include?(" ")
|
85
|
-
|
86
|
-
name.end_with?(".apkg") ? name[0, name.length - 5] : name
|
87
|
-
end
|
88
|
-
|
89
|
-
def setup_anki21_database_object
|
90
|
-
anki21_file_name = "collection.anki21"
|
91
|
-
db = SQLite3::Database.new "#{@tmpdir}/#{anki21_file_name}", options: {}
|
92
|
-
@tmp_files << anki21_file_name
|
93
|
-
db.execute_batch ANKI_SCHEMA_DEFINITION
|
94
|
-
db.execute CLEAN_COLLECTION_21_RECORD
|
95
|
-
db.results_as_hash = true
|
96
|
-
db
|
97
|
-
end
|
98
|
-
|
99
|
-
def setup_anki2_database_object
|
100
|
-
anki2_file_name = "collection.anki2"
|
101
|
-
db = SQLite3::Database.new "#{@tmpdir}/#{anki2_file_name}", options: {}
|
102
|
-
@tmp_files << anki2_file_name
|
103
|
-
db.execute_batch ANKI_SCHEMA_DEFINITION
|
104
|
-
db.execute CLEAN_COLLECTION_2_RECORD
|
105
|
-
db.close
|
106
|
-
db
|
107
|
-
end
|
108
|
-
|
109
|
-
def setup_media
|
110
|
-
media_file_path = FileUtils.touch("#{@tmpdir}/media")[0]
|
111
|
-
media_file = File.open(media_file_path, mode: "w")
|
112
|
-
media_file.write("{}")
|
113
|
-
media_file.close
|
114
|
-
@tmp_files << "media"
|
115
|
-
media_file
|
116
|
-
end
|
117
|
-
|
118
|
-
def puts_error_and_standard_message(error:)
|
119
|
-
puts "#{error}\n#{STANDARD_ERROR_MESSAGE}"
|
120
|
-
end
|
121
|
-
|
122
|
-
public
|
123
|
-
|
124
|
-
##
|
125
|
-
# Creates a new object which represents the Anki SQLite3 database file at +path+
|
126
|
-
#
|
127
|
-
# Development has focused on ::new so this method is not recommended at this time
|
128
|
-
def self.open(path:, target_directory: nil, &closure)
|
129
|
-
pathname = check_file_at_path_is_valid(path: path)
|
130
|
-
new_apkg_name = "#{File.basename(pathname.to_s, ".apkg")}-#{seconds_since_epoch}"
|
131
|
-
|
132
|
-
@anki_package = if target_directory
|
133
|
-
new(name: new_apkg_name, directory: target_directory)
|
134
|
-
else
|
135
|
-
new(name: new_apkg_name)
|
136
|
-
end
|
137
|
-
@anki_package.send :execute_closure_and_zip, @anki_package, &closure if block_given?
|
138
|
-
@anki_package
|
139
|
-
end
|
140
|
-
|
141
|
-
class << self
|
142
|
-
include TimeHelper
|
143
|
-
|
144
|
-
private
|
145
|
-
|
146
|
-
def check_file_at_path_is_valid(path:)
|
147
|
-
pathname = Pathname.new(path)
|
148
|
-
raise PATH_ERROR_MESSAGE unless pathname.file? && pathname.extname == ".apkg"
|
149
|
-
|
150
|
-
pathname
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
##
|
155
|
-
# Zips the temporary files into the *.apkg package and deletes the temporary files.
|
156
|
-
def zip
|
157
|
-
create_zip_file && destroy_temporary_directory
|
158
|
-
end
|
159
|
-
|
160
|
-
private
|
161
|
-
|
162
|
-
def create_zip_file
|
163
|
-
Zip::File.open(target_zip_file, create: true) do |zip_file|
|
164
|
-
@tmp_files.each do |file_name|
|
165
|
-
zip_file.add(file_name, File.join(@tmpdir, file_name))
|
166
|
-
end
|
167
|
-
end
|
168
|
-
true
|
169
|
-
end
|
170
|
-
|
171
|
-
def target_zip_file
|
172
|
-
"#{@directory}/#{@name}.apkg"
|
173
|
-
end
|
174
|
-
|
175
|
-
def destroy_temporary_directory
|
176
|
-
@anki21_database.close
|
177
|
-
FileUtils.rm_rf(@tmpdir)
|
178
|
-
end
|
179
|
-
|
180
|
-
public
|
181
|
-
|
182
|
-
##
|
183
|
-
# Returns true if the database is open
|
184
|
-
def open?
|
185
|
-
!closed?
|
186
|
-
end
|
187
|
-
|
188
|
-
##
|
189
|
-
# Returns true if the database is closed
|
190
|
-
def closed?
|
191
|
-
@anki21_database.closed?
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|