shunkuntype 1.1.0 → 1.1.2
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/.hyper_card/history/plot_modes.md +791 -0
- data/.hyper_card/history/plot_modes_251118.md +836 -0
- data/README.md +3 -14
- data/lib/shunkuntype/db.rb +9 -21
- data/lib/shunkuntype/speed.rb +12 -4
- data/lib/shunkuntype/version.rb +1 -1
- data/lib/shunkuntype.rb +97 -2
- metadata +5 -8
- data/shunkuntype/speed_data.txt +0 -6
- data/shunkuntype/training_data.txt +0 -36
|
@@ -0,0 +1,836 @@
|
|
|
1
|
+
# head
|
|
2
|
+
date: 2025-11-18
|
|
3
|
+
author: bob
|
|
4
|
+
|
|
5
|
+
## User
|
|
6
|
+
shunkuntype -c
|
|
7
|
+
Shunkuntype says 'Hello world'.
|
|
8
|
+
/Users/bob/.shunkuntype/speed_data.txt does not exist in this directory. --init or try in another dir.
|
|
9
|
+
とのエラー
|
|
10
|
+
|
|
11
|
+
## Copilot
|
|
12
|
+
このエラーは、`/Users/bob/.shunkuntype/speed_data.txt` というファイルが存在しないために発生しています。
|
|
13
|
+
`--init` オプションを使って初期化することで解決できます。
|
|
14
|
+
|
|
15
|
+
**コマンド例:**
|
|
16
|
+
```sh
|
|
17
|
+
shunkuntype --init
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
これで必要なファイルやディレクトリが作成され、エラーが解消されるはずです。
|
|
21
|
+
|
|
22
|
+
## User
|
|
23
|
+
--initがありません.
|
|
24
|
+
#file:speed.rb:31-31 からDataFilesに投げて,そのdirectoryを作るmethodを呼び出すように改良
|
|
25
|
+
|
|
26
|
+
## Copilot
|
|
27
|
+
以下のように修正できます。
|
|
28
|
+
|
|
29
|
+
1. speed.rb の `check_data_files` で `--init` が無い場合、`DataFiles.prepare` を呼び出して必要なディレクトリ・ファイルを作成するようにします。
|
|
30
|
+
2. `require_relative` で db.rb を読み込むようにします。
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
require "shunkuntype"
|
|
34
|
+
require_relative "db" # 追加
|
|
35
|
+
|
|
36
|
+
class SpeedCheck
|
|
37
|
+
attr_reader :number, :period
|
|
38
|
+
|
|
39
|
+
def initialize
|
|
40
|
+
@number = 20 #default 20
|
|
41
|
+
@period = 60
|
|
42
|
+
check_data_files
|
|
43
|
+
data = mk_random_words
|
|
44
|
+
t0, t1, count = exec_speed_check(data)
|
|
45
|
+
# ここでnilチェック
|
|
46
|
+
if t0.nil? || t1.nil?
|
|
47
|
+
puts "Speed check was interrupted or no input was given."
|
|
48
|
+
return
|
|
49
|
+
end
|
|
50
|
+
keep_record(t0, t1, count)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def print_keyboard
|
|
54
|
+
Shunkuntype.print_keyboard
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def check_data_files
|
|
58
|
+
begin
|
|
59
|
+
file = open(Shunkuntype::SPEED_FILE, "r")
|
|
60
|
+
if file
|
|
61
|
+
puts "#{Shunkuntype::SPEED_FILE} opened succcessfully"
|
|
62
|
+
end
|
|
63
|
+
rescue
|
|
64
|
+
puts "#{Shunkuntype::SPEED_FILE} does not exist in this directory. Creating required files..."
|
|
65
|
+
DataFiles.prepare # ここでDataFilesに投げる
|
|
66
|
+
retry # 再度ファイルオープンを試みる
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# ...existing code...
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
この修正で、`SPEED_FILE` が無い場合に自動で初期化処理が走るようになります。
|
|
74
|
+
|
|
75
|
+
## User
|
|
76
|
+
shunkuntype -c
|
|
77
|
+
Shunkuntype says 'Hello world'.
|
|
78
|
+
/Users/bob/.shunkuntype/speed_data.txt does not exist in this directory. Creating required files...
|
|
79
|
+
で無限loopに入ってます.
|
|
80
|
+
|
|
81
|
+
## Copilot
|
|
82
|
+
`check_data_files` の `retry` で、`DataFiles.prepare` がファイルを作成できていない場合、無限ループになります。
|
|
83
|
+
`DataFiles.prepare` が `Shunkuntype::SPEED_FILE` を必ず作成するように修正するか、`check_data_files` で `retry` の回数を制限しましょう。
|
|
84
|
+
|
|
85
|
+
**簡単な修正例(retryを1回だけにする):**
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
def check_data_files
|
|
89
|
+
retried = false
|
|
90
|
+
begin
|
|
91
|
+
file = open(Shunkuntype::SPEED_FILE, "r")
|
|
92
|
+
if file
|
|
93
|
+
puts "#{Shunkuntype::SPEED_FILE} opened succcessfully"
|
|
94
|
+
end
|
|
95
|
+
rescue
|
|
96
|
+
if retried
|
|
97
|
+
puts "Failed to create #{Shunkuntype::SPEED_FILE}. Please check permissions."
|
|
98
|
+
exit 1
|
|
99
|
+
end
|
|
100
|
+
puts "#{Shunkuntype::SPEED_FILE} does not exist in this directory. Creating required files..."
|
|
101
|
+
DataFiles.prepare
|
|
102
|
+
retried = true
|
|
103
|
+
retry
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
これで無限ループを防げます。
|
|
109
|
+
`DataFiles.prepare` 側も、`Shunkuntype::SPEED_FILE` を必ず作成するように見直すとより安全です。
|
|
110
|
+
|
|
111
|
+
## User
|
|
112
|
+
shunkuntype -c
|
|
113
|
+
Shunkuntype says 'Hello world'.
|
|
114
|
+
/Users/bob/.shunkuntype/speed_data.txt does not exist in this directory. Creating required files...
|
|
115
|
+
Failed to create /Users/bob/.shunkuntype/speed_data.txt. Please check permissions.
|
|
116
|
+
|
|
117
|
+
## Copilot
|
|
118
|
+
`DataFiles.prepare` で `speed_data.txt` を作成していないため、`check_data_files` の再試行でもファイルが存在せず、失敗しています。
|
|
119
|
+
|
|
120
|
+
**修正案:`speed_data.txt` も必ず作成するようにしましょう。**
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
module DataFiles
|
|
124
|
+
|
|
125
|
+
def self.prepare
|
|
126
|
+
speed_path = Shunkuntype::SPEED_FILE
|
|
127
|
+
training_path = Shunkuntype::TRAINING_FILE
|
|
128
|
+
|
|
129
|
+
create_file_if_not_exists(speed_path)
|
|
130
|
+
create_file_if_not_exists(training_path)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def self.create_file_if_not_exists(path)
|
|
134
|
+
create_file_path(path)
|
|
135
|
+
unless File.exist?(path)
|
|
136
|
+
File.open(path, 'w') {} # 空ファイルを作成
|
|
137
|
+
puts "Created #{path}"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def self.create_file_path(path)
|
|
142
|
+
FileUtils.mkdir_p File.dirname(path)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# create_data_filesは不要なので削除
|
|
146
|
+
private_class_method :create_file_if_not_exists, :create_file_path
|
|
147
|
+
end
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
この修正で、`speed_data.txt` と `training_data.txt` が必ず作成され、無限ループやエラーが解消されます。
|
|
151
|
+
|
|
152
|
+
## User
|
|
153
|
+
-pでtraining, speedデータを見やすく出力する機能を追加.
|
|
154
|
+
|
|
155
|
+
## Copilot
|
|
156
|
+
`-p` オプションで training, speed データを見やすく出力するには、
|
|
157
|
+
コマンドライン引数を判定し、各ファイルを整形して表示するメソッドを追加します。
|
|
158
|
+
|
|
159
|
+
例: `bin/shunkuntype` に以下を追加してください。
|
|
160
|
+
|
|
161
|
+
````ruby
|
|
162
|
+
# ...existing code...
|
|
163
|
+
|
|
164
|
+
if ARGV.include?('-p')
|
|
165
|
+
require_relative '../lib/shunkuntype'
|
|
166
|
+
require_relative '../lib/shunkuntype/db'
|
|
167
|
+
|
|
168
|
+
def print_csv_pretty(path, headers)
|
|
169
|
+
if File.exist?(path)
|
|
170
|
+
puts "== #{File.basename(path)} =="
|
|
171
|
+
puts headers.join("\t")
|
|
172
|
+
File.foreach(path) do |line|
|
|
173
|
+
puts line.chomp.split(',').join("\t")
|
|
174
|
+
end
|
|
175
|
+
puts
|
|
176
|
+
else
|
|
177
|
+
puts "#{path} not found."
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
print_csv_pretty(Shunkuntype::SPEED_FILE, %w[Time Words TimeSec Score])
|
|
182
|
+
print_csv_pretty(Shunkuntype::TRAINING_FILE, %w[Time Words TimeSec Score])
|
|
183
|
+
exit
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# ...existing code...
|
|
187
|
+
````
|
|
188
|
+
|
|
189
|
+
**ポイント**
|
|
190
|
+
- `print_csv_pretty` でカンマ区切りをタブ区切りで見やすく出力
|
|
191
|
+
- ファイルが無ければその旨を表示
|
|
192
|
+
- `-p` オプションでのみ実行
|
|
193
|
+
|
|
194
|
+
必要に応じてヘッダーや整形内容は調整してください。
|
|
195
|
+
|
|
196
|
+
## User
|
|
197
|
+
Commandの中のmethodにその機能を追加.
|
|
198
|
+
|
|
199
|
+
## Copilot
|
|
200
|
+
````ruby
|
|
201
|
+
class Command
|
|
202
|
+
|
|
203
|
+
def self.run(argv=[])
|
|
204
|
+
print "Shunkuntype says 'Hello world'.\n"
|
|
205
|
+
new(argv).execute
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def initialize(argv=[])
|
|
209
|
+
@argv = argv
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def execute
|
|
213
|
+
DataFiles.prepare
|
|
214
|
+
|
|
215
|
+
# -p オプションでデータ表示
|
|
216
|
+
if @argv.include?('-p')
|
|
217
|
+
print_pretty_data
|
|
218
|
+
exit
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
@argv << '--help' if @argv.size==0
|
|
222
|
+
command_parser = OptionParser.new do |opt|
|
|
223
|
+
opt.on('-v', '--version','show program Version.') { |v|
|
|
224
|
+
opt.version = Shunkuntype::VERSION
|
|
225
|
+
puts opt.ver
|
|
226
|
+
}
|
|
227
|
+
opt.on('-c', '--check','Check speed') {|v| SpeedCheck.new }
|
|
228
|
+
opt.on('-d', '--drill [VAL]','one minute Drill [VAL]', Integer) {|v| Training.new(v) }
|
|
229
|
+
opt.on('-h', '--help','show help message') { puts opt; exit }
|
|
230
|
+
opt.on('-l','--log','view training log') {|v| FinishCheck.new }
|
|
231
|
+
opt.on('-p', '--pretty','Show training and speed data in readable format') {}
|
|
232
|
+
end
|
|
233
|
+
command_parser.parse!(@argv)
|
|
234
|
+
exit
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
private
|
|
238
|
+
|
|
239
|
+
def print_pretty_data
|
|
240
|
+
print_csv_pretty(Shunkuntype::SPEED_FILE, %w[Time Words TimeSec Score])
|
|
241
|
+
print_csv_pretty(Shunkuntype::TRAINING_FILE, %w[Time Words TimeSec Score])
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def print_csv_pretty(path, headers)
|
|
245
|
+
if File.exist?(path)
|
|
246
|
+
puts "== #{File.basename(path)} =="
|
|
247
|
+
puts headers.join("\t")
|
|
248
|
+
File.foreach(path) do |line|
|
|
249
|
+
puts line.chomp.split(',').join("\t")
|
|
250
|
+
end
|
|
251
|
+
puts
|
|
252
|
+
else
|
|
253
|
+
puts "#{path} not found."
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
````
|
|
258
|
+
|
|
259
|
+
Similar code found with 1 license type
|
|
260
|
+
|
|
261
|
+
## User
|
|
262
|
+
-p=pythonとするとpythonのmatplotlibで描画するcodeを出力する機能を追加
|
|
263
|
+
|
|
264
|
+
## Copilot
|
|
265
|
+
````ruby
|
|
266
|
+
class Command
|
|
267
|
+
|
|
268
|
+
def execute
|
|
269
|
+
DataFiles.prepare
|
|
270
|
+
|
|
271
|
+
# -p オプションでデータ表示
|
|
272
|
+
if @argv.any? { |arg| arg.start_with?('-p') }
|
|
273
|
+
arg = @argv.find { |a| a.start_with?('-p') }
|
|
274
|
+
if arg.include?('=')
|
|
275
|
+
lang = arg.split('=', 2)[1]
|
|
276
|
+
if lang == 'python'
|
|
277
|
+
print_python_plot_code
|
|
278
|
+
else
|
|
279
|
+
puts "Unknown language for plotting: #{lang}"
|
|
280
|
+
end
|
|
281
|
+
else
|
|
282
|
+
print_pretty_data
|
|
283
|
+
end
|
|
284
|
+
exit
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
@argv << '--help' if @argv.size==0
|
|
288
|
+
command_parser = OptionParser.new do |opt|
|
|
289
|
+
opt.on('-v', '--version','show program Version.') { |v|
|
|
290
|
+
opt.version = Shunkuntype::VERSION
|
|
291
|
+
puts opt.ver
|
|
292
|
+
}
|
|
293
|
+
opt.on('-c', '--check','Check speed') {|v| SpeedCheck.new }
|
|
294
|
+
opt.on('-d', '--drill [VAL]','one minute Drill [VAL]', Integer) {|v| Training.new(v) }
|
|
295
|
+
opt.on('-h', '--help','show help message') { puts opt; exit }
|
|
296
|
+
opt.on('-l','--log','view training log') {|v| FinishCheck.new }
|
|
297
|
+
opt.on('-p', '--pretty','Show training and speed data in readable format or output matplotlib code with -p=python') {}
|
|
298
|
+
end
|
|
299
|
+
command_parser.parse!(@argv)
|
|
300
|
+
exit
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
private
|
|
304
|
+
|
|
305
|
+
def print_pretty_data
|
|
306
|
+
print_csv_pretty(Shunkuntype::SPEED_FILE, %w[Time Words TimeSec Score])
|
|
307
|
+
print_csv_pretty(Shunkuntype::TRAINING_FILE, %w[Time Words TimeSec Score])
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def print_csv_pretty(path, headers)
|
|
311
|
+
if File.exist?(path)
|
|
312
|
+
puts "== #{File.basename(path)} =="
|
|
313
|
+
puts headers.join("\t")
|
|
314
|
+
File.foreach(path) do |line|
|
|
315
|
+
puts line.chomp.split(',').join("\t")
|
|
316
|
+
end
|
|
317
|
+
puts
|
|
318
|
+
else
|
|
319
|
+
puts "#{path} not found."
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def print_python_plot_code
|
|
324
|
+
speed_path = Shunkuntype::SPEED_FILE
|
|
325
|
+
training_path = Shunkuntype::TRAINING_FILE
|
|
326
|
+
puts "# Python (matplotlib) code for plotting your typing data"
|
|
327
|
+
puts "# Save this as plot_typing.py and run: python plot_typing.py"
|
|
328
|
+
puts <<~PYTHON
|
|
329
|
+
import matplotlib.pyplot as plt
|
|
330
|
+
import csv
|
|
331
|
+
from datetime import datetime
|
|
332
|
+
|
|
333
|
+
def load_data(path):
|
|
334
|
+
times, scores = [], []
|
|
335
|
+
try:
|
|
336
|
+
with open(path) as f:
|
|
337
|
+
reader = csv.reader(f)
|
|
338
|
+
for row in reader:
|
|
339
|
+
if len(row) < 4:
|
|
340
|
+
continue
|
|
341
|
+
# row[0]: time, row[3]: score
|
|
342
|
+
try:
|
|
343
|
+
t = datetime.strptime(row[0].split('.')[0], '%Y-%m-%d %H:%M:%S')
|
|
344
|
+
s = float(row[3])
|
|
345
|
+
times.append(t)
|
|
346
|
+
scores.append(s)
|
|
347
|
+
except Exception:
|
|
348
|
+
continue
|
|
349
|
+
except FileNotFoundError:
|
|
350
|
+
pass
|
|
351
|
+
return times, scores
|
|
352
|
+
|
|
353
|
+
speed_times, speed_scores = load_data("#{speed_path}")
|
|
354
|
+
train_times, train_scores = load_data("#{training_path}")
|
|
355
|
+
|
|
356
|
+
plt.figure(figsize=(10,5))
|
|
357
|
+
if speed_times:
|
|
358
|
+
plt.plot(speed_times, speed_scores, 'o-', label='Speed')
|
|
359
|
+
if train_times:
|
|
360
|
+
plt.plot(train_times, train_scores, 's-', label='Training')
|
|
361
|
+
plt.xlabel('Date')
|
|
362
|
+
plt.ylabel('Score')
|
|
363
|
+
plt.title('Typing Progress')
|
|
364
|
+
plt.legend()
|
|
365
|
+
plt.grid(True)
|
|
366
|
+
plt.tight_layout()
|
|
367
|
+
plt.show()
|
|
368
|
+
PYTHON
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
````
|
|
372
|
+
|
|
373
|
+
Similar code found with 1 license type
|
|
374
|
+
|
|
375
|
+
## User
|
|
376
|
+
t = datetime.strptime(row[0].split('.')[0], '%Y-%m-%d %H:%M:%S')
|
|
377
|
+
を
|
|
378
|
+
2025-11-18 16:38:19 +0900
|
|
379
|
+
wo
|
|
380
|
+
|
|
381
|
+
## Copilot
|
|
382
|
+
`2025-11-18 16:38:19 +0900` のような日時文字列をPythonでパースするには、
|
|
383
|
+
`%Y-%m-%d %H:%M:%S %z` という書式を使います。
|
|
384
|
+
|
|
385
|
+
修正例:
|
|
386
|
+
|
|
387
|
+
````python
|
|
388
|
+
t = datetime.strptime(row[0].split('.')[0], '%Y-%m-%d %H:%M:%S %z')
|
|
389
|
+
````
|
|
390
|
+
|
|
391
|
+
このように `%z` を追加してください。
|
|
392
|
+
|
|
393
|
+
## User
|
|
394
|
+
-p plot, speed_data, training_dataでそれぞれの出力を調整するように修正
|
|
395
|
+
|
|
396
|
+
## Copilot
|
|
397
|
+
````ruby
|
|
398
|
+
class Command
|
|
399
|
+
|
|
400
|
+
def execute
|
|
401
|
+
DataFiles.prepare
|
|
402
|
+
|
|
403
|
+
# -p オプションでデータ表示
|
|
404
|
+
if @argv.any? { |arg| arg.start_with?('-p') }
|
|
405
|
+
arg = @argv.find { |a| a.start_with?('-p') }
|
|
406
|
+
if arg.include?('=')
|
|
407
|
+
mode = arg.split('=', 2)[1]
|
|
408
|
+
case mode
|
|
409
|
+
when 'python', 'plot'
|
|
410
|
+
print_python_plot_code
|
|
411
|
+
when 'speed_data'
|
|
412
|
+
print_csv_pretty(Shunkuntype::SPEED_FILE, %w[Time Words TimeSec Score])
|
|
413
|
+
when 'training_data'
|
|
414
|
+
print_csv_pretty(Shunkuntype::TRAINING_FILE, %w[Time Words TimeSec Score])
|
|
415
|
+
else
|
|
416
|
+
puts "Unknown -p option: #{mode}"
|
|
417
|
+
end
|
|
418
|
+
else
|
|
419
|
+
print_pretty_data
|
|
420
|
+
end
|
|
421
|
+
exit
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
@argv << '--help' if @argv.size==0
|
|
425
|
+
command_parser = OptionParser.new do |opt|
|
|
426
|
+
opt.on('-v', '--version','show program Version.') { |v|
|
|
427
|
+
opt.version = Shunkuntype::VERSION
|
|
428
|
+
puts opt.ver
|
|
429
|
+
}
|
|
430
|
+
opt.on('-c', '--check','Check speed') {|v| SpeedCheck.new }
|
|
431
|
+
opt.on('-d', '--drill [VAL]','one minute Drill [VAL]', Integer) {|v| Training.new(v) }
|
|
432
|
+
opt.on('-h', '--help','show help message') { puts opt; exit }
|
|
433
|
+
opt.on('-l','--log','view training log') {|v| FinishCheck.new }
|
|
434
|
+
opt.on('-p', '--pretty','Show training and speed data in readable format or output matplotlib code with -p=python/-p=plot/-p=speed_data/-p=training_data') {}
|
|
435
|
+
end
|
|
436
|
+
command_parser.parse!(@argv)
|
|
437
|
+
exit
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
private
|
|
441
|
+
|
|
442
|
+
def print_pretty_data
|
|
443
|
+
print_csv_pretty(Shunkuntype::SPEED_FILE, %w[Time Words TimeSec Score])
|
|
444
|
+
print_csv_pretty(Shunkuntype::TRAINING_FILE, %w[Time Words TimeSec Score])
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def print_csv_pretty(path, headers)
|
|
448
|
+
if File.exist?(path)
|
|
449
|
+
puts "== #{File.basename(path)} =="
|
|
450
|
+
puts headers.join("\t")
|
|
451
|
+
File.foreach(path) do |line|
|
|
452
|
+
puts line.chomp.split(',').join("\t")
|
|
453
|
+
end
|
|
454
|
+
puts
|
|
455
|
+
else
|
|
456
|
+
puts "#{path} not found."
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def print_python_plot_code
|
|
461
|
+
speed_path = Shunkuntype::SPEED_FILE
|
|
462
|
+
training_path = Shunkuntype::TRAINING_FILE
|
|
463
|
+
puts "# Python (matplotlib) code for plotting your typing data"
|
|
464
|
+
puts "# Save this as plot_typing.py and run: python plot_typing.py"
|
|
465
|
+
puts <<~PYTHON
|
|
466
|
+
import matplotlib.pyplot as plt
|
|
467
|
+
import csv
|
|
468
|
+
from datetime import datetime
|
|
469
|
+
|
|
470
|
+
def load_data(path):
|
|
471
|
+
times, scores = [], []
|
|
472
|
+
try:
|
|
473
|
+
with open(path) as f:
|
|
474
|
+
reader = csv.reader(f)
|
|
475
|
+
for row in reader:
|
|
476
|
+
if len(row) < 4:
|
|
477
|
+
continue
|
|
478
|
+
# row[0]: time, row[3]: score
|
|
479
|
+
try:
|
|
480
|
+
t = datetime.strptime(row[0].split('.')[0], '%Y-%m-%d %H:%M:%S %z')
|
|
481
|
+
s = float(row[2])
|
|
482
|
+
times.append(t)
|
|
483
|
+
scores.append(s)
|
|
484
|
+
except Exception:
|
|
485
|
+
continue
|
|
486
|
+
except FileNotFoundError:
|
|
487
|
+
pass
|
|
488
|
+
return times, scores
|
|
489
|
+
|
|
490
|
+
speed_times, speed_scores = load_data("#{speed_path}")
|
|
491
|
+
train_times, train_scores = load_data("#{training_path}")
|
|
492
|
+
|
|
493
|
+
plt.figure(figsize=(10,5))
|
|
494
|
+
if speed_times:
|
|
495
|
+
plt.plot(speed_times, speed_scores, 'o-', label='Speed')
|
|
496
|
+
if train_times:
|
|
497
|
+
plt.plot(train_times, train_scores, 's-', label='Training')
|
|
498
|
+
plt.xlabel('Date')
|
|
499
|
+
plt.ylabel('Score')
|
|
500
|
+
plt.title('Typing Progress')
|
|
501
|
+
plt.legend()
|
|
502
|
+
plt.grid(True)
|
|
503
|
+
plt.tight_layout()
|
|
504
|
+
plt.show()
|
|
505
|
+
PYTHON
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
````
|
|
509
|
+
|
|
510
|
+
Similar code found with 1 license type
|
|
511
|
+
|
|
512
|
+
## User
|
|
513
|
+
=は含まず,OptionParserの通常の処理に変更
|
|
514
|
+
|
|
515
|
+
## Copilot
|
|
516
|
+
````ruby
|
|
517
|
+
class Command
|
|
518
|
+
|
|
519
|
+
def self.run(argv=[])
|
|
520
|
+
new(argv).execute
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def initialize(argv=[])
|
|
524
|
+
@argv = argv
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def execute
|
|
528
|
+
DataFiles.prepare
|
|
529
|
+
|
|
530
|
+
@mode = nil
|
|
531
|
+
|
|
532
|
+
command_parser = OptionParser.new do |opt|
|
|
533
|
+
opt.on('-v', '--version','show program Version.') { |v|
|
|
534
|
+
opt.version = Shunkuntype::VERSION
|
|
535
|
+
puts opt.ver
|
|
536
|
+
}
|
|
537
|
+
opt.on('-c', '--check','Check speed') {|v| SpeedCheck.new }
|
|
538
|
+
opt.on('-d', '--drill [VAL]','one minute Drill [VAL]', Integer) {|v| Training.new(v) }
|
|
539
|
+
opt.on('-h', '--help','show help message') { puts opt; exit }
|
|
540
|
+
opt.on('-l','--log','view training log') {|v| FinishCheck.new }
|
|
541
|
+
opt.on('-p MODE', '--pretty MODE', 'Show data: plot, speed_data, training_data, or all') {|mode| @mode = mode }
|
|
542
|
+
opt.on('-p', '--pretty', 'Show training and speed data in readable format') { @mode = 'all' }
|
|
543
|
+
end
|
|
544
|
+
command_parser.parse!(@argv)
|
|
545
|
+
# データ表示
|
|
546
|
+
case @mode
|
|
547
|
+
when 'plot', 'python'
|
|
548
|
+
print_python_plot_code
|
|
549
|
+
exit
|
|
550
|
+
when 'speed_data'
|
|
551
|
+
print_csv_pretty(Shunkuntype::SPEED_FILE, %w[Time Words TimeSec Score])
|
|
552
|
+
exit
|
|
553
|
+
when 'training_data'
|
|
554
|
+
print_csv_pretty(Shunkuntype::TRAINING_FILE, %w[Time Words TimeSec Score])
|
|
555
|
+
exit
|
|
556
|
+
when 'all'
|
|
557
|
+
print_pretty_data
|
|
558
|
+
exit
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
@argv << '--help' if @argv.size==0
|
|
562
|
+
command_parser.parse!(@argv)
|
|
563
|
+
exit
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
private
|
|
567
|
+
|
|
568
|
+
def print_pretty_data
|
|
569
|
+
print_csv_pretty(Shunkuntype::SPEED_FILE, %w[Time Words TimeSec Score])
|
|
570
|
+
print_csv_pretty(Shunkuntype::TRAINING_FILE, %w[Time Words TimeSec Score])
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
def print_csv_pretty(path, headers)
|
|
574
|
+
if File.exist?(path)
|
|
575
|
+
puts "== #{File.basename(path)} =="
|
|
576
|
+
puts headers.join("\t")
|
|
577
|
+
File.foreach(path) do |line|
|
|
578
|
+
puts line.chomp.split(',').join("\t")
|
|
579
|
+
end
|
|
580
|
+
puts
|
|
581
|
+
else
|
|
582
|
+
puts "#{path} not found."
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
def print_python_plot_code
|
|
587
|
+
speed_path = Shunkuntype::SPEED_FILE
|
|
588
|
+
training_path = Shunkuntype::TRAINING_FILE
|
|
589
|
+
puts "# Python (matplotlib) code for plotting your typing data"
|
|
590
|
+
puts "# Save this as plot_typing.py and run: python plot_typing.py"
|
|
591
|
+
puts <<~PYTHON
|
|
592
|
+
import matplotlib.pyplot as plt
|
|
593
|
+
import csv
|
|
594
|
+
from datetime import datetime
|
|
595
|
+
|
|
596
|
+
def load_data(path):
|
|
597
|
+
times, scores = [], []
|
|
598
|
+
try:
|
|
599
|
+
with open(path) as f:
|
|
600
|
+
reader = csv.reader(f)
|
|
601
|
+
for row in reader:
|
|
602
|
+
if len(row) < 4:
|
|
603
|
+
continue
|
|
604
|
+
# row[0]: time, row[2]: score
|
|
605
|
+
try:
|
|
606
|
+
t = datetime.strptime(row[0].split('.')[0], '%Y-%m-%d %H:%M:%S %z')
|
|
607
|
+
s = float(row[2])
|
|
608
|
+
times.append(t)
|
|
609
|
+
scores.append(s)
|
|
610
|
+
except Exception:
|
|
611
|
+
continue
|
|
612
|
+
except FileNotFoundError:
|
|
613
|
+
pass
|
|
614
|
+
return times, scores
|
|
615
|
+
|
|
616
|
+
speed_times, speed_scores = load_data("#{speed_path}")
|
|
617
|
+
train_times, train_scores = load_data("#{training_path}")
|
|
618
|
+
|
|
619
|
+
plt.figure(figsize=(10,5))
|
|
620
|
+
if speed_times:
|
|
621
|
+
plt.plot(speed_times, speed_scores, 'o-', label='Speed')
|
|
622
|
+
if train_times:
|
|
623
|
+
plt.plot(train_times, train_scores, 's-', label='Training')
|
|
624
|
+
plt.xlabel('Date')
|
|
625
|
+
plt.ylabel('Score')
|
|
626
|
+
plt.title('Typing Progress')
|
|
627
|
+
plt.legend()
|
|
628
|
+
plt.grid(True)
|
|
629
|
+
plt.tight_layout()
|
|
630
|
+
plt.show()
|
|
631
|
+
PYTHON
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
````
|
|
635
|
+
|
|
636
|
+
Similar code found with 1 license type
|
|
637
|
+
|
|
638
|
+
## User
|
|
639
|
+
-pのとりうるoptionをhelpに明示.
|
|
640
|
+
|
|
641
|
+
## Copilot
|
|
642
|
+
````ruby
|
|
643
|
+
command_parser = OptionParser.new do |opt|
|
|
644
|
+
opt.on('-v', '--version','show program Version.') { |v|
|
|
645
|
+
opt.version = Shunkuntype::VERSION
|
|
646
|
+
puts opt.ver
|
|
647
|
+
}
|
|
648
|
+
opt.on('-c', '--check','Check speed') {|v| SpeedCheck.new }
|
|
649
|
+
opt.on('-d', '--drill [VAL]','one minute Drill [VAL]', Integer) {|v| Training.new(v) }
|
|
650
|
+
opt.on('-h', '--help','show help message') { puts opt; exit }
|
|
651
|
+
opt.on('-l','--log','view training log') {|v| FinishCheck.new }
|
|
652
|
+
opt.on('-p MODE', '--pretty MODE',
|
|
653
|
+
"Show data in readable format. MODE can be:\n" \
|
|
654
|
+
" all Show both speed and training data (default)\n" \
|
|
655
|
+
" plot Output matplotlib code for plotting\n" \
|
|
656
|
+
" speed_data Show only speed data\n" \
|
|
657
|
+
" training_data Show only training data"
|
|
658
|
+
) {|mode| @mode = mode }
|
|
659
|
+
opt.on('-p', '--pretty', 'Show both speed and training data (same as -p all)') { @mode = 'all' }
|
|
660
|
+
end
|
|
661
|
+
````
|
|
662
|
+
|
|
663
|
+
Similar code found with 1 license type
|
|
664
|
+
|
|
665
|
+
## User
|
|
666
|
+
-pをやめて, -r, --recordに変更
|
|
667
|
+
|
|
668
|
+
## Copilot
|
|
669
|
+
````ruby
|
|
670
|
+
class Command
|
|
671
|
+
|
|
672
|
+
def self.run(argv=[])
|
|
673
|
+
new(argv).execute
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
def initialize(argv=[])
|
|
677
|
+
@argv = argv
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
def execute
|
|
681
|
+
DataFiles.prepare
|
|
682
|
+
|
|
683
|
+
@mode = nil
|
|
684
|
+
|
|
685
|
+
command_parser = OptionParser.new do |opt|
|
|
686
|
+
opt.on('-v', '--version','show program Version.') { |v|
|
|
687
|
+
opt.version = Shunkuntype::VERSION
|
|
688
|
+
puts opt.ver
|
|
689
|
+
}
|
|
690
|
+
opt.on('-c', '--check','Check speed') {|v| SpeedCheck.new }
|
|
691
|
+
opt.on('-d', '--drill [VAL]','one minute Drill [VAL]', Integer) {|v| Training.new(v) }
|
|
692
|
+
opt.on('-h', '--help','show help message') { puts opt; exit }
|
|
693
|
+
opt.on('-l','--log','view training log') {|v| FinishCheck.new }
|
|
694
|
+
opt.on('-r MODE', '--record MODE',
|
|
695
|
+
"Show record data in readable format. MODE can be:\n" \
|
|
696
|
+
" all Show both speed and training data (default)\n" \
|
|
697
|
+
" plot Output matplotlib code for plotting\n" \
|
|
698
|
+
" speed_data Show only speed data\n" \
|
|
699
|
+
" training_data Show only training data"
|
|
700
|
+
) {|mode| @mode = mode }
|
|
701
|
+
opt.on('-r', '--record', 'Show both speed and training data (same as -r all)') { @mode = 'all' }
|
|
702
|
+
end
|
|
703
|
+
command_parser.parse!(@argv)
|
|
704
|
+
# データ表示
|
|
705
|
+
case @mode
|
|
706
|
+
when 'plot', 'python'
|
|
707
|
+
print_python_plot_code
|
|
708
|
+
exit
|
|
709
|
+
when 'speed_data'
|
|
710
|
+
print_csv_pretty(Shunkuntype::SPEED_FILE, %w[Time Words TimeSec Score])
|
|
711
|
+
exit
|
|
712
|
+
when 'training_data'
|
|
713
|
+
print_csv_pretty(Shunkuntype::TRAINING_FILE, %w[Time Words TimeSec Score])
|
|
714
|
+
exit
|
|
715
|
+
when 'all'
|
|
716
|
+
print_pretty_data
|
|
717
|
+
exit
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
@argv << '--help' if @argv.size==0
|
|
721
|
+
command_parser.parse!(@argv)
|
|
722
|
+
exit
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
private
|
|
726
|
+
|
|
727
|
+
def print_pretty_data
|
|
728
|
+
print_csv_pretty(Shunkuntype::SPEED_FILE, %w[Time Words TimeSec Score])
|
|
729
|
+
print_csv_pretty(Shunkuntype::TRAINING_FILE, %w[Time Words TimeSec Score])
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
def print_csv_pretty(path, headers)
|
|
733
|
+
if File.exist?(path)
|
|
734
|
+
puts "== #{File.basename(path)} =="
|
|
735
|
+
puts headers.join("\t")
|
|
736
|
+
File.foreach(path) do |line|
|
|
737
|
+
puts line.chomp.split(',').join("\t")
|
|
738
|
+
end
|
|
739
|
+
puts
|
|
740
|
+
else
|
|
741
|
+
puts "#{path} not found."
|
|
742
|
+
end
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
def print_python_plot_code
|
|
746
|
+
speed_path = Shunkuntype::SPEED_FILE
|
|
747
|
+
training_path = Shunkuntype::TRAINING_FILE
|
|
748
|
+
puts "# Python (matplotlib) code for plotting your typing data"
|
|
749
|
+
puts "# Save this as plot_typing.py and run: python plot_typing.py"
|
|
750
|
+
puts <<~PYTHON
|
|
751
|
+
import matplotlib.pyplot as plt
|
|
752
|
+
import csv
|
|
753
|
+
from datetime import datetime
|
|
754
|
+
|
|
755
|
+
def load_data(path):
|
|
756
|
+
times, scores = [], []
|
|
757
|
+
try:
|
|
758
|
+
with open(path) as f:
|
|
759
|
+
reader = csv.reader(f)
|
|
760
|
+
for row in reader:
|
|
761
|
+
if len(row) < 4:
|
|
762
|
+
continue
|
|
763
|
+
# row[0]: time, row[2]: score
|
|
764
|
+
try:
|
|
765
|
+
t = datetime.strptime(row[0].split('.')[0], '%Y-%m-%d %H:%M:%S %z')
|
|
766
|
+
s = float(row[2])
|
|
767
|
+
times.append(t)
|
|
768
|
+
scores.append(s)
|
|
769
|
+
except Exception:
|
|
770
|
+
continue
|
|
771
|
+
except FileNotFoundError:
|
|
772
|
+
pass
|
|
773
|
+
return times, scores
|
|
774
|
+
|
|
775
|
+
speed_times, speed_scores = load_data("#{speed_path}")
|
|
776
|
+
train_times, train_scores = load_data("#{training_path}")
|
|
777
|
+
|
|
778
|
+
plt.figure(figsize=(10,5))
|
|
779
|
+
if speed_times:
|
|
780
|
+
plt.plot(speed_times, speed_scores, 'o-', label='Speed')
|
|
781
|
+
if train_times:
|
|
782
|
+
plt.plot(train_times, train_scores, 's-', label='Training')
|
|
783
|
+
plt.xlabel('Date')
|
|
784
|
+
plt.ylabel('Score')
|
|
785
|
+
plt.title('Typing Progress')
|
|
786
|
+
plt.legend()
|
|
787
|
+
plt.grid(True)
|
|
788
|
+
plt.tight_layout()
|
|
789
|
+
plt.show()
|
|
790
|
+
PYTHON
|
|
791
|
+
end
|
|
792
|
+
end
|
|
793
|
+
````
|
|
794
|
+
|
|
795
|
+
Similar code found with 1 license type
|
|
796
|
+
|
|
797
|
+
## User
|
|
798
|
+
#terminalSelection なんで,opt.onのargumentが間違ってない?
|
|
799
|
+
|
|
800
|
+
## Copilot
|
|
801
|
+
Read terminal selection
|
|
802
|
+
|
|
803
|
+
`opt.on('-r MODE', '--record MODE', ...)` の書き方は正しいです。
|
|
804
|
+
`OptionParser` では、`-r MODE` や `--record MODE` のように、引数を取るオプションはこのように記述します。
|
|
805
|
+
|
|
806
|
+
**例:**
|
|
807
|
+
```ruby
|
|
808
|
+
opt.on('-r MODE', '--record MODE', '説明') {|mode| ... }
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
この場合、`shunkuntype -r plot` や `shunkuntype --record speed_data` のように使えます。
|
|
812
|
+
|
|
813
|
+
`-r` だけで引数なしの場合は、
|
|
814
|
+
```ruby
|
|
815
|
+
opt.on('-r', '--record', '説明') { ... }
|
|
816
|
+
```
|
|
817
|
+
と書きます。
|
|
818
|
+
|
|
819
|
+
**まとめ:**
|
|
820
|
+
`opt.on('-r MODE', '--record MODE', ...)` は正しい記法です。
|
|
821
|
+
`-r` だけで使う場合は、引数なしの `opt.on('-r', '--record', ...)` も併用しているので問題ありません。
|
|
822
|
+
|
|
823
|
+
## User
|
|
824
|
+
helpが出ないので,
|
|
825
|
+
``` ruby
|
|
826
|
+
opt.on('-r MODE', '--record MODE',
|
|
827
|
+
"Show record data in readable format.\n" \
|
|
828
|
+
" MODE can be:\n" \
|
|
829
|
+
" all Show both speed and training data (default)\n" \
|
|
830
|
+
" plot Output matplotlib code for plotting\n" \
|
|
831
|
+
" speed_data Show only speed data\n" \
|
|
832
|
+
" training_data Show only training data"
|
|
833
|
+
) {|mode| @mode = mode }
|
|
834
|
+
# opt.on('-r', '--record', 'Show both speed and training data (same as -r all)') { @mode = 'all' }
|
|
835
|
+
```
|
|
836
|
+
と修正.
|