anki_record 0.3.2 → 0.4.1
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 +15 -1
- data/Gemfile +4 -0
- data/Gemfile.lock +9 -2
- data/README.md +89 -132
- data/anki_record.gemspec +2 -2
- data/lib/anki_record/anki21_database/anki21_database.rb +172 -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 +115 -174
- 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 +72 -93
- 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
@@ -4,7 +4,6 @@ require "json"
|
|
4
4
|
|
5
5
|
require_relative "../deck/deck"
|
6
6
|
require_relative "../deck_options_group/deck_options_group"
|
7
|
-
require_relative "../helpers/data_query_helper"
|
8
7
|
require_relative "../helpers/time_helper"
|
9
8
|
require_relative "../note_type/note_type"
|
10
9
|
require_relative "collection_attributes"
|
@@ -12,171 +11,38 @@ require_relative "collection_attributes"
|
|
12
11
|
module AnkiRecord
|
13
12
|
##
|
14
13
|
# Collection represents the single record in the Anki collection.anki21 database's `col` table.
|
15
|
-
# The note types, decks, and deck options groups data are contained within this record
|
14
|
+
# The note types, decks, and deck options groups data are contained within this record, but
|
15
|
+
# for simplicity of the gem's API, they are managed by the Anki21Database class.
|
16
16
|
class Collection
|
17
|
-
include Helpers::DataQueryHelper
|
18
17
|
include Helpers::TimeHelper
|
19
18
|
include CollectionAttributes
|
20
19
|
|
21
|
-
|
22
|
-
setup_collection_instance_variables(anki_package: anki_package)
|
23
|
-
end
|
24
|
-
|
25
|
-
def add_note_type(note_type) # :nodoc:
|
26
|
-
raise ArgumentError unless note_type.instance_of?(AnkiRecord::NoteType)
|
27
|
-
|
28
|
-
existing_note_type = nil
|
29
|
-
@note_types.each do |nt|
|
30
|
-
existing_note_type = nt if nt.id == note_type.id
|
31
|
-
end
|
32
|
-
@note_types.delete(existing_note_type) if existing_note_type
|
33
|
-
|
34
|
-
@note_types << note_type
|
35
|
-
end
|
36
|
-
|
37
|
-
def add_deck(deck) # :nodoc:
|
38
|
-
raise ArgumentError unless deck.instance_of?(AnkiRecord::Deck)
|
39
|
-
|
40
|
-
@decks << deck
|
41
|
-
end
|
42
|
-
|
43
|
-
def add_deck_options_group(deck_options_group) # :nodoc:
|
44
|
-
raise ArgumentError unless deck_options_group.instance_of?(AnkiRecord::DeckOptionsGroup)
|
45
|
-
|
46
|
-
@deck_options_groups << deck_options_group
|
47
|
-
end
|
48
|
-
|
49
|
-
##
|
50
|
-
# Returns the collection's note type object found by either +name+ or +id+, or nil if it is not found.
|
51
|
-
def find_note_type_by(name: nil, id: nil)
|
52
|
-
if (id && name) || (id.nil? && name.nil?)
|
53
|
-
raise ArgumentError,
|
54
|
-
"You must pass either an id or name keyword argument."
|
55
|
-
end
|
56
|
-
|
57
|
-
name ? find_note_type_by_name(name: name) : find_note_type_by_id(id: id)
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
def find_note_type_by_name(name:)
|
63
|
-
note_types.find { |note_type| note_type.name == name }
|
64
|
-
end
|
65
|
-
|
66
|
-
def find_note_type_by_id(id:)
|
67
|
-
note_types.find { |note_type| note_type.id == id }
|
68
|
-
end
|
69
|
-
|
70
|
-
public
|
71
|
-
|
72
|
-
##
|
73
|
-
# Returns the collection's deck object found by either +name+ or +id+, or nil if it is not found.
|
74
|
-
def find_deck_by(name: nil, id: nil)
|
75
|
-
if (id && name) || (id.nil? && name.nil?)
|
76
|
-
raise ArgumentError,
|
77
|
-
"You must pass either an id or name keyword argument."
|
78
|
-
end
|
79
|
-
|
80
|
-
name ? find_deck_by_name(name: name) : find_deck_by_id(id: id)
|
81
|
-
end
|
82
|
-
|
83
|
-
private
|
84
|
-
|
85
|
-
def find_deck_by_name(name:)
|
86
|
-
decks.find { |deck| deck.name == name }
|
87
|
-
end
|
88
|
-
|
89
|
-
def find_deck_by_id(id:)
|
90
|
-
decks.find { |deck| deck.id == id }
|
91
|
-
end
|
92
|
-
|
93
|
-
public
|
94
|
-
|
95
|
-
##
|
96
|
-
# Returns the collection's deck options group object found by +id+, or nil if it is not found.
|
97
|
-
def find_deck_options_group_by(id:)
|
98
|
-
deck_options_groups.find { |deck_options_group| deck_options_group.id == id }
|
99
|
-
end
|
100
|
-
|
101
|
-
##
|
102
|
-
# Returns the collection's note object found by +id+, or nil if it is not found.
|
103
|
-
def find_note_by(id:)
|
104
|
-
note_cards_data = note_cards_data_for_note_id sql_able: anki_package, id: id
|
105
|
-
return nil unless note_cards_data
|
106
|
-
|
107
|
-
AnkiRecord::Note.new collection: self, data: note_cards_data
|
108
|
-
end
|
109
|
-
|
110
|
-
# :nodoc:
|
111
|
-
def decks_json
|
112
|
-
JSON.parse(anki_package.prepare("select decks from col;").execute.first["decks"])
|
113
|
-
end
|
20
|
+
attr_reader :anki21_database
|
114
21
|
|
22
|
+
# rubocop:disable Metrics/AbcSize
|
23
|
+
# rubocop:disable Metrics/MethodLength
|
115
24
|
# :nodoc:
|
116
|
-
def
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
@
|
122
|
-
|
123
|
-
|
25
|
+
def initialize(anki21_database:)
|
26
|
+
@anki21_database = anki21_database
|
27
|
+
@id = col_record["id"]
|
28
|
+
@created_at_timestamp = col_record["crt"]
|
29
|
+
@last_modified_timestamp = col_record["mod"]
|
30
|
+
@scm = col_record["scm"]
|
31
|
+
@ver = col_record["ver"]
|
32
|
+
@dty = col_record["dty"]
|
33
|
+
@usn = col_record["usn"]
|
34
|
+
@ls = col_record["ls"]
|
35
|
+
@configuration = JSON.parse(col_record["conf"])
|
36
|
+
@tags = JSON.parse(col_record["tags"])
|
124
37
|
remove_instance_variable(:@col_record)
|
125
38
|
end
|
39
|
+
# rubocop:enable Metrics/AbcSize
|
40
|
+
# rubocop:enable Metrics/MethodLength
|
126
41
|
|
127
42
|
private
|
128
43
|
|
129
|
-
def setup_collection_instance_variables(anki_package:)
|
130
|
-
@anki_package = anki_package
|
131
|
-
setup_simple_collaborator_objects
|
132
|
-
setup_custom_collaborator_objects
|
133
|
-
remove_instance_variable(:@col_record)
|
134
|
-
end
|
135
|
-
|
136
44
|
def col_record
|
137
|
-
@col_record ||=
|
138
|
-
end
|
139
|
-
|
140
|
-
# rubocop:disable Metrics/AbcSize
|
141
|
-
def setup_simple_collaborator_objects
|
142
|
-
@id = col_record["id"]
|
143
|
-
@created_at_timestamp = col_record["crt"]
|
144
|
-
@last_modified_timestamp = col_record["mod"]
|
145
|
-
@scm = col_record["scm"]
|
146
|
-
@ver = col_record["ver"]
|
147
|
-
@dty = col_record["dty"]
|
148
|
-
@usn = col_record["usn"]
|
149
|
-
@ls = col_record["ls"]
|
150
|
-
@configuration = JSON.parse(col_record["conf"])
|
151
|
-
@tags = JSON.parse(col_record["tags"])
|
152
|
-
end
|
153
|
-
# rubocop:enable Metrics/AbcSize
|
154
|
-
|
155
|
-
def setup_custom_collaborator_objects
|
156
|
-
setup_note_type_collaborators
|
157
|
-
setup_deck_options_groups_collaborators
|
158
|
-
setup_deck_collaborators
|
159
|
-
end
|
160
|
-
|
161
|
-
def setup_note_type_collaborators
|
162
|
-
@note_types = []
|
163
|
-
JSON.parse(col_record["models"]).values.map do |model_hash|
|
164
|
-
NoteType.new(collection: self, args: model_hash)
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def setup_deck_collaborators
|
169
|
-
@decks = []
|
170
|
-
JSON.parse(col_record["decks"]).values.map do |deck_hash|
|
171
|
-
Deck.new(collection: self, args: deck_hash)
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
def setup_deck_options_groups_collaborators
|
176
|
-
@deck_options_groups = []
|
177
|
-
JSON.parse(col_record["dconf"]).values.map do |dconf_hash|
|
178
|
-
DeckOptionsGroup.new(collection: self, args: dconf_hash)
|
179
|
-
end
|
45
|
+
@col_record ||= anki21_database.col_record
|
180
46
|
end
|
181
47
|
end
|
182
48
|
end
|
@@ -1,35 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AnkiRecord
|
4
|
-
|
5
|
-
|
6
|
-
module CollectionAttributes
|
7
|
-
##
|
8
|
-
# The collection's Anki package object.
|
9
|
-
attr_reader :anki_package
|
10
|
-
|
11
|
-
##
|
12
|
-
# The collection's id, which is also the id of the col record in the collection.anki21 database (usually 1).
|
13
|
-
attr_reader :id
|
14
|
-
|
15
|
-
##
|
16
|
-
# The number of milliseconds since the 1970 epoch when the collection record was created.
|
17
|
-
attr_reader :created_at_timestamp
|
18
|
-
|
19
|
-
##
|
20
|
-
# The number of milliseconds since the 1970 epoch at which the collection record was last modified.
|
21
|
-
attr_reader :last_modified_timestamp
|
22
|
-
|
23
|
-
##
|
24
|
-
# The collection's note type objects as an array.
|
25
|
-
attr_reader :note_types
|
26
|
-
|
27
|
-
##
|
28
|
-
# The collection's deck objects as an array
|
29
|
-
attr_reader :decks
|
30
|
-
|
31
|
-
##
|
32
|
-
# The collection's deck option group objects as an array.
|
33
|
-
attr_reader :deck_options_groups
|
4
|
+
module CollectionAttributes # :nodoc:
|
5
|
+
attr_reader :id, :created_at_timestamp, :last_modified_timestamp
|
34
6
|
end
|
35
7
|
end
|
@@ -15,28 +15,28 @@ module AnkiRecord
|
|
15
15
|
include Helpers::TimeHelper
|
16
16
|
|
17
17
|
##
|
18
|
-
# Instantiates a new Deck object belonging to +
|
19
|
-
def initialize(
|
18
|
+
# Instantiates a new Deck object belonging to +anki21_database+ with name +name+.
|
19
|
+
def initialize(anki21_database:, name: nil, args: nil)
|
20
20
|
raise ArgumentError unless (name && args.nil?) || (args && args["name"])
|
21
21
|
|
22
|
-
@
|
22
|
+
@anki21_database = anki21_database
|
23
23
|
if args
|
24
|
-
setup_deck_instance_variables_from_existing(args:
|
24
|
+
setup_deck_instance_variables_from_existing(args:)
|
25
25
|
else
|
26
|
-
setup_deck_instance_variables(name:
|
26
|
+
setup_deck_instance_variables(name:)
|
27
27
|
end
|
28
28
|
|
29
|
-
@
|
29
|
+
@anki21_database.add_deck self
|
30
30
|
save if args
|
31
31
|
end
|
32
32
|
|
33
33
|
##
|
34
34
|
# Saves the deck to the collection.anki21 database.
|
35
35
|
def save
|
36
|
-
collection_decks_hash =
|
36
|
+
collection_decks_hash = anki21_database.decks_json
|
37
37
|
collection_decks_hash[@id] = to_h
|
38
38
|
sql = "update col set decks = ? where id = ?"
|
39
|
-
|
39
|
+
anki21_database.prepare(sql).execute([JSON.generate(collection_decks_hash), anki21_database.collection.id])
|
40
40
|
end
|
41
41
|
|
42
42
|
def to_h # :nodoc:
|
@@ -54,6 +54,7 @@ module AnkiRecord
|
|
54
54
|
def inspect
|
55
55
|
"#<AnkiRecord::Deck:#{object_id} id: #{id} name: #{name} description: #{description}>"
|
56
56
|
end
|
57
|
+
# :nocov:
|
57
58
|
|
58
59
|
private
|
59
60
|
|
@@ -72,7 +73,7 @@ module AnkiRecord
|
|
72
73
|
@collapsed_in_browser = args["browserCollapsed"]
|
73
74
|
@description = args["desc"]
|
74
75
|
@dyn = args["dyn"]
|
75
|
-
@deck_options_group =
|
76
|
+
@deck_options_group = anki21_database.find_deck_options_group_by(id: args["conf"])
|
76
77
|
@extend_new = args["extendNew"]
|
77
78
|
@extend_review = args["extendRev"]
|
78
79
|
end
|
@@ -90,7 +91,7 @@ module AnkiRecord
|
|
90
91
|
@collapsed_in_browser = default_collapsed
|
91
92
|
@description = ""
|
92
93
|
@dyn = NON_FILTERED_DECK_DYN
|
93
|
-
@deck_options_group =
|
94
|
+
@deck_options_group = anki21_database.find_deck_options_group_by id: default_deck_options_group_id
|
94
95
|
@extend_new = 0
|
95
96
|
@extend_review = 0
|
96
97
|
end
|
@@ -3,28 +3,26 @@
|
|
3
3
|
module AnkiRecord
|
4
4
|
# Module with the Deck class's attribute readers, writers, and accessors.
|
5
5
|
module DeckAttributes
|
6
|
-
|
7
|
-
# The deck's collection object.
|
8
|
-
attr_reader :collection
|
6
|
+
attr_reader :anki21_database # :nodoc:
|
9
7
|
|
10
8
|
##
|
11
|
-
# The deck's name
|
9
|
+
# The deck's name
|
12
10
|
attr_accessor :name
|
13
11
|
|
14
12
|
##
|
15
|
-
# The deck's description
|
13
|
+
# The deck's description
|
16
14
|
attr_accessor :description
|
17
15
|
|
18
16
|
##
|
19
|
-
# The deck's id
|
17
|
+
# The deck's id
|
20
18
|
attr_reader :id
|
21
19
|
|
22
20
|
##
|
23
|
-
# The number of seconds since the 1970 epoch when the deck was last modified
|
21
|
+
# The number of seconds since the 1970 epoch when the deck was last modified
|
24
22
|
attr_reader :last_modified_timestamp
|
25
23
|
|
26
24
|
##
|
27
|
-
# The deck's deck options group
|
25
|
+
# The deck's deck options group
|
28
26
|
attr_reader :deck_options_group
|
29
27
|
end
|
30
28
|
end
|
@@ -7,25 +7,27 @@ require_relative "deck_options_group_attributes"
|
|
7
7
|
module AnkiRecord
|
8
8
|
##
|
9
9
|
# DeckOptionsGroup represents a set of options that can be applied to an Anki deck.
|
10
|
+
# The interface to this has not been explored much so using the gem with these directly
|
11
|
+
# may involve breaking encapsulation.
|
10
12
|
class DeckOptionsGroup
|
11
13
|
include DeckOptionsGroupAttributes
|
12
14
|
include Helpers::SharedConstantsHelper
|
13
15
|
include Helpers::TimeHelper
|
14
16
|
|
15
17
|
##
|
16
|
-
# Instantiates a new deck options group belonging to +
|
17
|
-
def initialize(
|
18
|
+
# Instantiates a new deck options group belonging to +anki21_database+ with name +name+.
|
19
|
+
def initialize(anki21_database:, name: nil, args: nil)
|
18
20
|
raise ArgumentError unless (name && args.nil?) || (args && args["name"])
|
19
21
|
|
20
|
-
@
|
22
|
+
@anki21_database = anki21_database
|
21
23
|
|
22
24
|
if args
|
23
|
-
setup_deck_options_group_instance_variables_from_existing(args:
|
25
|
+
setup_deck_options_group_instance_variables_from_existing(args:)
|
24
26
|
else
|
25
|
-
setup_deck_options_group_instance_variables(name:
|
27
|
+
setup_deck_options_group_instance_variables(name:)
|
26
28
|
end
|
27
29
|
|
28
|
-
@
|
30
|
+
@anki21_database.add_deck_options_group self
|
29
31
|
end
|
30
32
|
|
31
33
|
private
|
@@ -2,22 +2,20 @@
|
|
2
2
|
|
3
3
|
module AnkiRecord
|
4
4
|
##
|
5
|
-
# Module with
|
5
|
+
# Module with DeckOptionsGroup's attribute readers, writers, and accessors.
|
6
6
|
module DeckOptionsGroupAttributes
|
7
|
-
|
8
|
-
# The deck options group's collection object.
|
9
|
-
attr_reader :collection
|
7
|
+
attr_reader :anki21_database # :nodoc:
|
10
8
|
|
11
9
|
##
|
12
|
-
# The deck option group's name
|
10
|
+
# The deck option group's name
|
13
11
|
attr_accessor :name
|
14
12
|
|
15
13
|
##
|
16
|
-
# The deck option group's id
|
14
|
+
# The deck option group's id
|
17
15
|
attr_reader :id
|
18
16
|
|
19
17
|
##
|
20
|
-
# The number of milliseconds since the 1970 epoch at which the deck options group was modified
|
18
|
+
# The number of milliseconds since the 1970 epoch at which the deck options group was modified
|
21
19
|
attr_reader :last_modified_timestamp
|
22
20
|
end
|
23
21
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest"
|
4
|
+
|
5
|
+
module AnkiRecord
|
6
|
+
module Helpers
|
7
|
+
##
|
8
|
+
# A module for the method that computes the guid value of notes.
|
9
|
+
#
|
10
|
+
# This guid is used by Anki when importing a deck package to update existing notes
|
11
|
+
# rather than create duplicates of them.
|
12
|
+
module AnkiGuidHelper
|
13
|
+
##
|
14
|
+
# Returns a random string of 10 characters sliced out of a random base64 string (RFC 3548).
|
15
|
+
def self.globally_unique_id
|
16
|
+
SecureRandom.base64(9).slice(1, 10)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnkiRecord
|
4
|
+
class Media # :nodoc:
|
5
|
+
attr_reader :anki_package, :media_file
|
6
|
+
|
7
|
+
FILENAME = "media"
|
8
|
+
|
9
|
+
def self.create_new(anki_package:)
|
10
|
+
media = new
|
11
|
+
media.create_initialize(anki_package:)
|
12
|
+
media
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_initialize(anki_package:)
|
16
|
+
@anki_package = anki_package
|
17
|
+
media_file_path = FileUtils.touch("#{anki_package.tmpdir}/#{FILENAME}")[0]
|
18
|
+
@media_file = File.open(media_file_path, mode: "w")
|
19
|
+
media_file.write("{}")
|
20
|
+
media_file.close
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.update_new(anki_package:)
|
24
|
+
media = new
|
25
|
+
media.update_initialize(anki_package:)
|
26
|
+
media
|
27
|
+
end
|
28
|
+
|
29
|
+
def update_initialize(anki_package:)
|
30
|
+
@anki_package = anki_package
|
31
|
+
@media_file = File.open("#{anki_package.tmpdir}/#{FILENAME}", mode: "w")
|
32
|
+
media_file.write("{}")
|
33
|
+
media_file.close
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|