renshuu 0.1.0 → 1.0.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.
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renshuu
4
+ ##
5
+ # Model class for term presence.
6
+ class Presence < Model
7
+ ##
8
+ # Model class for schedule presence.
9
+ class Schedule < Model
10
+ ##
11
+ # @!attribute [r] sched_id
12
+ # @return [String]
13
+ attribute :sched_id, Types::String
14
+ alias schedule_id sched_id
15
+ ##
16
+ # @!attribute [r] name
17
+ # @return [String]
18
+ attribute :name, Types::String
19
+ ##
20
+ # @!attribute [r] has_word
21
+ # @return [Boolean]
22
+ attribute :has_word, Types::Params::Bool
23
+ alias has_word? has_word
24
+
25
+ ##
26
+ # Retrieves the schedule associated with this presence item.
27
+ #
28
+ # @return [Renshuu::Schedule]
29
+ #
30
+ # @see Renshuu::Schedule.get
31
+ def schedule
32
+ Renshuu::Schedule.get(schedule_id)
33
+ end
34
+ end
35
+ ##
36
+ # @!attribute [r] scheds
37
+ # @return [Array<Schedule>]
38
+ attribute(:scheds, Types::Array.of(Schedule).default { [] })
39
+ alias schedules scheds
40
+
41
+ ##
42
+ # Model class for list presence.
43
+ class List < Model
44
+ ##
45
+ # @!attribute [r] list_id
46
+ # @return [String]
47
+ attribute :list_id, Types::String
48
+ ##
49
+ # @!attribute [r] name
50
+ # @return [String]
51
+ attribute :name, Types::String
52
+ ##
53
+ # @!attribute [r] set_name
54
+ # @return [String, nil]
55
+ attribute? :set_name, Types::String
56
+ ##
57
+ # @!attribute [r] has_word
58
+ # @return [Boolean]
59
+ attribute :has_word, Types::Params::Bool
60
+ end
61
+ ##
62
+ # @!attribute [r] lists
63
+ # @return [Array<List>]
64
+ attribute(:lists, Types::Array.of(List).default { [] })
65
+ end
66
+ end
@@ -12,5 +12,163 @@ module Renshuu
12
12
  body = Renshuu.client.query(:get, 'v1/profile')
13
13
  new(body)
14
14
  end
15
+
16
+ ##
17
+ # @!attribute [r] id
18
+ # @return [String]
19
+ attribute :id, Types::String
20
+ ##
21
+ # @!attribute [r] real_name
22
+ # @return [String]
23
+ attribute :real_name, Types::String
24
+ ##
25
+ # @!attribute [r] adventure_level
26
+ # @return [String]
27
+ attribute :adventure_level, Types::String
28
+ ##
29
+ # @!attribute [r] user_length
30
+ # @return [String]
31
+ attribute :user_length, Types::String
32
+ ##
33
+ # @!attribute [r] kao
34
+ # @return [URI]
35
+ attribute :kao, Types::URI
36
+
37
+ ##
38
+ # Model class for user study counts.
39
+ class StudyCount < Model
40
+ ##
41
+ # @!attribute [r] today_all
42
+ # @return [Integer]
43
+ attribute :today_all, Types::Integer
44
+ ##
45
+ # @!attribute [r] today_grammar
46
+ # @return [Integer]
47
+ attribute :today_grammar, Types::Integer
48
+ ##
49
+ # @!attribute [r] today_vocab
50
+ # @return [Integer]
51
+ attribute :today_vocab, Types::Integer
52
+ ##
53
+ # @!attribute [r] today_kanji
54
+ # @return [Integer]
55
+ attribute :today_kanji, Types::Integer
56
+ ##
57
+ # @!attribute [r] today_sent
58
+ # @return [Integer]
59
+ attribute :today_sent, Types::Integer
60
+ ##
61
+ # @!attribute [r] today_aconj
62
+ # @return [Integer]
63
+ attribute :today_aconj, Types::Integer
64
+ ##
65
+ # @!attribute [r] today_conj
66
+ # @return [Integer]
67
+ attribute :today_conj, Types::Integer
68
+ ##
69
+ # @!attribute [r] total
70
+ # @return [Integer]
71
+ attribute :total, Types::Integer
72
+ ##
73
+ # @!attribute [r] total_vocab
74
+ # @return [Integer]
75
+ attribute :total_vocab, Types::Integer
76
+ ##
77
+ # @!attribute [r] total_kanji
78
+ # @return [Integer]
79
+ attribute :total_kanji, Types::Integer
80
+ ##
81
+ # @!attribute [r] total_grammar
82
+ # @return [Integer]
83
+ attribute :total_grammar, Types::Integer
84
+ ##
85
+ # @!attribute [r] total_sent
86
+ # @return [Integer]
87
+ attribute :total_sent, Types::Integer
88
+ end
89
+ ##
90
+ # @!attribute [r] studied
91
+ # @return [StudyCount]
92
+ attribute :studied, StudyCount
93
+ alias study_count studied
94
+
95
+ ##
96
+ # Model class for profile JLPT progress.
97
+ class Progress < Model
98
+ ##
99
+ # @!attribute [r] n1
100
+ # @return [Integer]
101
+ attribute :n1, Types::Integer
102
+ ##
103
+ # @!attribute [r] n2
104
+ # @return [Integer]
105
+ attribute :n2, Types::Integer
106
+ ##
107
+ # @!attribute [r] n3
108
+ # @return [Integer]
109
+ attribute :n3, Types::Integer
110
+ ##
111
+ # @!attribute [r] n4
112
+ # @return [Integer]
113
+ attribute :n4, Types::Integer
114
+ ##
115
+ # @!attribute [r] n5
116
+ # @return [Integer]
117
+ attribute :n5, Types::Integer
118
+ end
119
+ ##
120
+ # @!attribute [r] level_progress_percs
121
+ # @return [Hash{String => Progress}]
122
+ attribute :level_progress_percs, Types::Hash.map(Types::String, Progress)
123
+
124
+ ##
125
+ # Model class for profile study streaks.
126
+ class Streak < Model
127
+ ##
128
+ # @!attribute [r] correct_in_a_row
129
+ # @return [Integer]
130
+ attribute :correct_in_a_row, Types::Integer
131
+ ##
132
+ # @!attribute [r] correct_in_a_row_alltime
133
+ # @return [Integer]
134
+ attribute :correct_in_a_row_alltime, Types::Integer
135
+ ##
136
+ # @!attribute [r] days_studied_in_a_row
137
+ # @return [Integer]
138
+ attribute :days_studied_in_a_row, Types::Integer
139
+ ##
140
+ # @!attribute [r] days_studied_in_a_row_alltime
141
+ # @return [Integer]
142
+ attribute :days_studied_in_a_row_alltime, Types::Integer
143
+ end
144
+ ##
145
+ # @!attribute [r] streaks
146
+ # @return [Hash{String => Streak}]
147
+ attribute :streaks, Types::Hash.map(Types::String, Streak)
148
+
149
+ ##
150
+ # Model class for profile API usage.
151
+ class APIUsage < Model
152
+ ##
153
+ # @!attribute [r] calls_today
154
+ # @return [Integer]
155
+ attribute :calls_today, Types::Integer
156
+ ##
157
+ # @!attribute [r] daily_allowance
158
+ # @return [Integer]
159
+ attribute :daily_allowance, Types::Integer
160
+
161
+ ##
162
+ # Number of API calls remaining in today's quota.
163
+ #
164
+ # @return [Integer]
165
+ def remaining_today
166
+ daily_allowance - calls_today
167
+ end
168
+ end
169
+ ##
170
+ # @!attribute [r] api_usage
171
+ # @return [APIUsage]
172
+ attribute :api_usage, APIUsage
15
173
  end
16
174
  end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renshuu
4
+ ##
5
+ # Model class for schedules.
6
+ #
7
+ # @see https://api.renshuu.org/docs/#/Schedules
8
+ class Schedule < Model
9
+ ##
10
+ # Mapping between schedule booktypes and model classes.
11
+ #
12
+ # @see #term_class
13
+ BOOKTYPES = {
14
+ 'kanji' => Kanji, 'vocab' => Word, 'grammar' => Grammar,
15
+ 'sent' => Sentence
16
+ }.freeze
17
+
18
+ ##
19
+ # Lists all schedules of the current user.
20
+ #
21
+ # @return [Array<Schedule>]
22
+ #
23
+ # @see https://api.renshuu.org/docs/#/Schedules/get_schedule
24
+ def self.list
25
+ body = Renshuu.client.query(:get, 'v1/schedule')
26
+
27
+ body.fetch(:schedules).map { new(_1) }
28
+ end
29
+
30
+ ##
31
+ # Retrieves a specific schedule from the API.
32
+ #
33
+ # @param [String, Integer] id
34
+ # @return [Schedule]
35
+ #
36
+ # @see https://api.renshuu.org/docs/#/Schedules/get_schedule__id_
37
+ def self.get(id)
38
+ body = Renshuu.client.query(:get, "v1/schedule/#{id}")
39
+
40
+ body.fetch(:schedules).first.then { new(_1) }
41
+ end
42
+
43
+ ##
44
+ # @!attribute [r] id
45
+ # @return [String]
46
+ attribute :id, Types::String
47
+ ##
48
+ # @!attribute [r] name
49
+ # @return [String]
50
+ attribute :name, Types::String
51
+ ##
52
+ # @!attribute [r] booktype
53
+ # @return [String]
54
+ attribute :booktype, Types::String
55
+ ##
56
+ # @!attribute [r] is_frozen
57
+ # @return [Boolean]
58
+ attribute :is_frozen, Types::Params::Bool
59
+
60
+ ##
61
+ # Model class for schedules today counts.
62
+ class TodayCount < Model
63
+ transform_keys do |key|
64
+ key == :new ? :new_terms : key
65
+ end
66
+
67
+ ##
68
+ # @!attribute [r] review
69
+ # @return [Integer]
70
+ attribute :review, Types::Integer
71
+ ##
72
+ # @!attribute [r] new_terms
73
+ # @note +new+ is a reserved keyword, so we have to circumvent that.
74
+ # @return [Integer]
75
+ attribute :new_terms, Types::Integer
76
+ end
77
+ ##
78
+ # @!attribute [r] today
79
+ # rn [TodayCount]
80
+ attribute :today, TodayCount
81
+
82
+ ##
83
+ # Model class for schedule upcoming counts.
84
+ class UpcomingCount < Model
85
+ ##
86
+ # @!attribute [r] days_in_future
87
+ # @return [Integer]
88
+ attribute :days_in_future, Types::Integer
89
+ ##
90
+ # @!attribute [r] terms_to_review
91
+ # @return [Integer]
92
+ attribute :terms_to_review, Types::Integer
93
+ end
94
+ ##
95
+ # @!attribute [r] upcoming
96
+ # @return [Array<UpcomingCount>]
97
+ attribute :upcoming, Types::Array.of(UpcomingCount)
98
+
99
+ ##
100
+ # Model class for schedule term counts.
101
+ class TermCount < Model
102
+ ##
103
+ # @!attribute [r] total_count
104
+ # @return [Integer]
105
+ attribute :total_count, Types::Integer
106
+ ##
107
+ # @!attribute [r] studied_count
108
+ # @return [Integer]
109
+ attribute :studied_count, Types::Integer
110
+ ##
111
+ # @!attribute [r] unstudied_count
112
+ # @return [Integer]
113
+ attribute :unstudied_count, Types::Integer
114
+ ##
115
+ # @!attribute [r] hidden_count
116
+ # @return [Integer]
117
+ attribute :hidden_count, Types::Integer
118
+ end
119
+ ##
120
+ # @!attribute [r] terms
121
+ # @return [TermCount]
122
+ attribute :terms, TermCount
123
+
124
+ ##
125
+ # Model class for schedule new term counts.
126
+ class NewTermCount < Model
127
+ ##
128
+ # @!attribute [r] today_count
129
+ # @return [Integer]
130
+ attribute :today_count, Types::Integer
131
+ ##
132
+ # @!attribute [r] rolling_week_count
133
+ # @return [Integer]
134
+ attribute :rolling_week_count, Types::Integer
135
+ end
136
+ ##
137
+ # @!attribute [r] new_terms
138
+ # @return [NewTermCount]
139
+ attribute :new_terms, NewTermCount
140
+
141
+ ##
142
+ # Retrieves the terms contained in the schedule.
143
+ #
144
+ # @param [Integer, nil] page
145
+ # @param [Symbol, nil] group One of +:all+, +:blocked+, +:studied+,
146
+ # +:notyetstudied+, +:cannot_study+, +:review_today+, +:mastery_1+,
147
+ # +:mastery_2+, +:mastery_3+, +:mastery_4+, +:mastery_5+, +:mastery_6+,
148
+ # +:mastery_7+, +:mastery_8+ or +:mastery_9+.
149
+ #
150
+ # @return [Array<Kanji,Word,Grammar,Sentence>] Depending on the type of
151
+ # schedule
152
+ def contents(page: nil, group: nil)
153
+ params = { pg: page, group: }.compact
154
+ body = Renshuu.client.query(:get, "v1/schedule/#{id}/list", params:)
155
+
156
+ body.dig(:contents, :terms).map { term_class.new(_1) }
157
+ end
158
+
159
+ ##
160
+ # Adds the given item to the schedule.
161
+ #
162
+ # @param [Schedulable] item
163
+ #
164
+ # @return [Void]
165
+ # @raise [ArgumentError] If the type of the item does not match the
166
+ # schedule's type
167
+ #
168
+ # @see Schedulable#add_to_schedule
169
+ def add(item)
170
+ unless item.is_a?(term_class)
171
+ raise ArgumentError, "Expected a `#{term_class}`, got a `#{item.class}`"
172
+ end
173
+
174
+ item.add_to_schedule(self)
175
+ end
176
+
177
+ ##
178
+ # Removes the given item from the schedule.
179
+ #
180
+ # @param [Schedulable] item
181
+ #
182
+ # @return [Void]
183
+ # @raise [ArgumentError] If the type of the item does not match the
184
+ # schedule's type
185
+ #
186
+ # @see Schedulable#remove_from_schedule
187
+ def remove(item)
188
+ unless item.is_a?(term_class)
189
+ raise ArgumentError, "Expected a `#{term_class}`, got a `#{item.class}`"
190
+ end
191
+
192
+ item.remove_from_schedule(self)
193
+ end
194
+
195
+ ##
196
+ # Model class to use for this schedule's terms.
197
+ #
198
+ # @return [Class]
199
+ # @raise [KeyError]
200
+ #
201
+ # @see ::BOOKTYPES
202
+ def term_class
203
+ @term_class ||= BOOKTYPES.fetch(booktype) do |key|
204
+ raise KeyError, "Unkown schedule type `#{key}`"
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renshuu
4
+ ##
5
+ # Model class for sentences.
6
+ #
7
+ # @see https://api.renshuu.org/docs/#/Sentence
8
+ class Sentence < Model
9
+ ##
10
+ # Searches sentences in Renshuu's dictionary.
11
+ #
12
+ # @param [String] value
13
+ #
14
+ # @return [Array<Sentence>]
15
+ #
16
+ # @see https://api.renshuu.org/docs/#/Sentence/get_reibun_search
17
+ def self.search(value)
18
+ Renshuu.client.query(:get, 'v1/reibun/search', params: { value: })
19
+ .fetch(:reibuns).map { new(_1) }
20
+ end
21
+
22
+ ##
23
+ # Retrieves sentences from Renshuu's dictionary for a specific word.
24
+ #
25
+ # @param [Word, Integer] word
26
+ #
27
+ # @return [Array<Sentence>]
28
+ #
29
+ # @see https://api.renshuu.org/docs/#/Sentence/get_reibun_search__word_id_
30
+ def self.word_search(word)
31
+ identifier = case word
32
+ when Integer then word
33
+ else word.id
34
+ end
35
+ Renshuu.client.query(:get, "v1/reibun/search/#{identifier}")
36
+ .fetch(:reibuns).map { new(_1) }
37
+ end
38
+
39
+ ##
40
+ # @!attribute [r] id
41
+ # @return [String]
42
+ attribute :id, Types::String
43
+ ##
44
+ # @!attribute [r] japanese
45
+ # @return [String]
46
+ attribute :japanese, Types::String
47
+ ##
48
+ # @!attribute [r] hiragana
49
+ # @return [String, nil]
50
+ attribute? :hiragana, Types::String
51
+ ##
52
+ # @!attribute [r] meaning
53
+ # @return [Hash{String => String}]
54
+ attribute :meaning, Types::Hash.map(Types::String, Types::String)
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renshuu
4
+ ##
5
+ # Module for type values.
6
+ module Types
7
+ include Dry.Types(default: :coercible)
8
+
9
+ ##
10
+ # Dry type for dates.
11
+ Date = Constructor(::Date) do |date|
12
+ case date
13
+ when ::Date then date
14
+ when 'Not yet' then nil
15
+ when 'Now' then ::Date.today
16
+ else ::Date.parse(date)
17
+ end
18
+ end
19
+
20
+ ##
21
+ # Dry type for URIs.
22
+ URI = Constructor(URI) do |uri|
23
+ ::URI.parse(uri)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renshuu
4
+ ##
5
+ # Model class for user data.
6
+ class UserData < Model
7
+ ##
8
+ # @!attribute [r] correct_count
9
+ # @return [Integer]
10
+ attribute :correct_count, Types::Integer
11
+ ##
12
+ # @!attribute [r] missed_count
13
+ # @return [Integer]
14
+ attribute :missed_count, Types::Integer
15
+ ##
16
+ # @!attribute [r] mastery_avg_perc
17
+ # @return [Integer]
18
+ attribute :mastery_avg_perc, Types::Integer.fallback(0)
19
+
20
+ ##
21
+ # Model class for user data study vectors.
22
+ class StudyVector < Model
23
+ ##
24
+ # @!attribute [r] name
25
+ # @return [String]
26
+ attribute :name, Types::String
27
+ ##
28
+ # @!attribute [r] correct_count
29
+ # @return [Integer]
30
+ attribute :correct_count, Types::Integer
31
+ ##
32
+ # @!attribute [r] missed_count
33
+ # @return [Integer]
34
+ attribute :missed_count, Types::Integer
35
+ ##
36
+ # @!attribute [r] mastery_perc
37
+ # @return [Integer]
38
+ attribute :mastery_perc, Types::Integer
39
+ ##
40
+ # @!attribute [r] last_quizzed
41
+ # @return [Date]
42
+ attribute :last_quizzed, Types::Date
43
+ ##
44
+ # @!attribute [r] next_quiz
45
+ # @return [Date]
46
+ attribute :next_quiz, Types::Date
47
+ end
48
+ ##
49
+ # @!attribute [r] study_vectors
50
+ # @return [Hash{String => StudyVector}]
51
+ attribute? :study_vectors, Types::Hash.map(Types::String, StudyVector)
52
+ end
53
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renshuu
4
+ ##
5
+ # Model class for words and vocabulary.
6
+ #
7
+ # @see https://api.renshuu.org/docs/#/Vocabulary
8
+ class Word < Model
9
+ include Schedulable
10
+ include Listable
11
+
12
+ ##
13
+ # Searches the Renshuu vocabulary dictionary.
14
+ #
15
+ # @param [String] value
16
+ #
17
+ # @return [Array<Word>]
18
+ def self.search(value)
19
+ body = Renshuu.client.query(:get, 'v1/word/search', params: { value: })
20
+
21
+ body.fetch(:words).map { new(_1) }
22
+ end
23
+
24
+ ##
25
+ # Retrieves a word from the dictionary by its identifier.
26
+ #
27
+ # @param [Integer] id
28
+ #
29
+ # @return [Word]
30
+ def self.get(id)
31
+ body = Renshuu.client.query(:get, "v1/word/#{id}")
32
+
33
+ body.fetch(:words).first.then { new(_1) }
34
+ end
35
+
36
+ ##
37
+ # @!attribute [r] id
38
+ # @return [String]
39
+ attribute :id, Types::String
40
+ ##
41
+ # @!attribute [r] kanji_full
42
+ # @return [String]
43
+ attribute :kanji_full, Types::String
44
+ ##
45
+ # @!attribute [r] hiragana_full
46
+ # @return [String]
47
+ attribute :hiragana_full, Types::String
48
+ ##
49
+ # @!attribute [r] pic
50
+ # @return [Array<URI>]
51
+ attribute(:pic, Types::Array.of(Types::URI).default { [] })
52
+ alias pictures pic
53
+ ##
54
+ # @!attribute [r] markers
55
+ # @return [Array<String>]
56
+ attribute(:markers, Types::Array.of(Types::String).default { [] })
57
+ ##
58
+ # @!attribute [r] pitch
59
+ # @return [Array<String>]
60
+ attribute :pitch, Types::Array.of(Types::String)
61
+ ##
62
+ # @!attribute [r] typeofspeech
63
+ # @return [String]
64
+ attribute :typeofspeech, Types::String
65
+ alias type_of_speech typeofspeech
66
+ ##
67
+ # @!attribute [r] def
68
+ # @return [Array<String>]
69
+ attribute :def, Types::Array.of(Types::String)
70
+
71
+ ##
72
+ # @!attribute [r] user_data
73
+ # @return [UserData, nil]
74
+ attribute? :user_data, UserData
75
+ ##
76
+ # @!attribute [r] presence
77
+ # @return [Presence, nil]
78
+ attribute? :presence, Presence
79
+
80
+ ##
81
+ # Retrieves sentences using this word from the dictionary.
82
+ #
83
+ # @return [Array<Sentence>]
84
+ #
85
+ # @see Sentence.word_search
86
+ def sentences
87
+ Sentence.word_search(self)
88
+ end
89
+ end
90
+ end
@@ -3,9 +3,25 @@
3
3
  module Renshuu # rubocop:disable Style/Documentation
4
4
  end
5
5
 
6
+ # Types
7
+ require_relative 'models/types'
8
+
9
+ # Mixins
10
+ require_relative 'models/mixins/listable'
11
+ require_relative 'models/mixins/schedulable'
12
+
6
13
  # Base class
7
14
  require_relative 'models/model'
8
15
 
9
- # Other models
16
+ # Other models (respect blank lines for dependency order)
17
+ require_relative 'models/presence'
18
+ require_relative 'models/user_data'
19
+
20
+ require_relative 'models/grammar'
10
21
  require_relative 'models/kanji'
11
22
  require_relative 'models/profile'
23
+ require_relative 'models/sentence'
24
+ require_relative 'models/word'
25
+
26
+ require_relative 'models/list'
27
+ require_relative 'models/schedule'
@@ -3,5 +3,5 @@
3
3
  module Renshuu
4
4
  ##
5
5
  # Semantic version number of the library.
6
- VERSION = '0.1.0'
6
+ VERSION = '1.0.0'
7
7
  end
data/lib/renshuu.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # External requires
4
+ require 'dry-struct'
4
5
  require 'httpx'
5
- require 'recursive-open-struct'
6
6
  require 'uri'
7
7
 
8
8
  # Pre-module requires