anki_record 0.2.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|