mk_semi_lattice 0.1.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.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.hyper_card/history/20251020_1517_readme_formatted.md +463 -0
  4. data/.hyper_card/history/20251020_1828_minor_rev_formatted.md +181 -0
  5. data/.hyper_card/history/20251021_1110_wsl_ubuntu_japanese.md +218 -0
  6. data/.hyper_card/history/20251021_1142_double_click_action.md +250 -0
  7. data/.hyper_card/history/20251021_1151_select_semi_data_action.md +75 -0
  8. data/.hyper_card/history/20251021_2322_add_comment_edges.md +252 -0
  9. data/.hyper_card/history/20251022_0725_add_color_index.md +155 -0
  10. data/.hyper_card/history/classify_251103.md +1090 -0
  11. data/.hyper_card/history/file_path_modifier.md +481 -0
  12. data/.hyper_card/history/file_path_modifier_251103.md +505 -0
  13. data/.hyper_card/history/knowledge_fixer.pdf +0 -0
  14. data/.hyper_card/history/log_with_symbol_251102.md +184 -0
  15. data/.hyper_card/history/memory_leak_251028.md +311 -0
  16. data/.hyper_card/history/set_log_conf_251027.md +295 -0
  17. data/CHANGELOG.md +5 -0
  18. data/CODE_OF_CONDUCT.md +132 -0
  19. data/LICENSE.txt +21 -0
  20. data/README.md +43 -0
  21. data/Rakefile +8 -0
  22. data/exe/mk_semi_lattice +3 -0
  23. data/exe/sl +5 -0
  24. data/lib/mk_semi_lattice/config.rb +53 -0
  25. data/lib/mk_semi_lattice/log.rb +26 -0
  26. data/lib/mk_semi_lattice/manage_yaml.rb +89 -0
  27. data/lib/mk_semi_lattice/mk_dir_yaml.rb +40 -0
  28. data/lib/mk_semi_lattice/mk_node_edge.rb +108 -0
  29. data/lib/mk_semi_lattice/mk_semi_lattice_graph.rb +254 -0
  30. data/lib/mk_semi_lattice/mk_semi_lattice_viewer.rb +77 -0
  31. data/lib/mk_semi_lattice/option_manager.rb +58 -0
  32. data/lib/mk_semi_lattice/ruby2d_action.rb +136 -0
  33. data/lib/mk_semi_lattice/version.rb +5 -0
  34. data/lib/mk_semi_lattice.rb +22 -0
  35. data/sig/mk_semi_lattice.rbs +4 -0
  36. metadata +122 -0
@@ -0,0 +1,1090 @@
1
+ # head
2
+ date: 2025-11-03
3
+ author: bob
4
+
5
+ ## User
6
+ codeのrefactorを進めます.まずは,config関連の動作をclass Configにまとめて呼び出すようにしましょう.
7
+
8
+ ## Copilot
9
+ 以下のように`Config`クラスを新設し、設定ファイルの読み書きやディレクトリ作成、ログ有効化/無効化の管理をまとめます。
10
+ グローバル変数`$conf`や`CONF_PATH`なども`Config`クラスのクラス変数/メソッドで管理します。
11
+
12
+ ````ruby
13
+ # ...existing code...
14
+
15
+ class Config
16
+ CONFIG_DIR = File.expand_path("~/.config/semi_lattice")
17
+ CONF_PATH = File.join(CONFIG_DIR, "semi_lattice.conf")
18
+ LOG_PATH = File.join(CONFIG_DIR, "semi_lattice_history")
19
+
20
+ @conf = { "log" => false }
21
+
22
+ class << self
23
+ attr_reader :conf
24
+
25
+ def setup
26
+ FileUtils.mkdir_p(CONFIG_DIR)
27
+ load_conf
28
+ end
29
+
30
+ def load_conf
31
+ if File.file?(CONF_PATH)
32
+ begin
33
+ loaded = YAML.load_file(CONF_PATH)
34
+ if loaded.is_a?(Hash)
35
+ @conf.merge!(loaded)
36
+ else
37
+ puts "Warning: #{CONF_PATH} is not a hash. Using default config.".yellow
38
+ end
39
+ rescue
40
+ puts "Warning: #{CONF_PATH} is invalid. Using default config.".yellow
41
+ end
42
+ end
43
+ end
44
+
45
+ def save_conf
46
+ File.write(CONF_PATH, @conf.to_yaml)
47
+ end
48
+
49
+ def log_enabled?
50
+ @conf["log"]
51
+ end
52
+
53
+ def set_log(value)
54
+ @conf["log"] = value
55
+ save_conf
56
+ end
57
+
58
+ def log_path
59
+ LOG_PATH
60
+ end
61
+ end
62
+ end
63
+
64
+ Config.setup
65
+
66
+ # ...existing code...
67
+
68
+ options = { layer: 2, init_step: :from_semi_lattice, show_index: false, merge: false }
69
+ OptionParser.new do |opts|
70
+ # ...existing code...
71
+ opts.on("-l", "--log [BOOL]", "Enable/disable logging (true/false), and save to config") do |v|
72
+ bool =
73
+ if v.nil?
74
+ true
75
+ elsif v.is_a?(String)
76
+ case v.strip.downcase
77
+ when "true", "yes", "on", "1"
78
+ true
79
+ when "false", "no", "off", "0"
80
+ false
81
+ else
82
+ puts "Invalid value for log: #{v}. Using default: false".yellow
83
+ false
84
+ end
85
+ else
86
+ !!v
87
+ end
88
+ Config.set_log(bool)
89
+ puts "Logging is now #{bool ? 'enabled' : 'disabled'} (saved to #{Config::CONF_PATH})"
90
+ exit
91
+ end
92
+ end.parse!
93
+
94
+ # ...existing code...
95
+
96
+ def log_event(action, target_dir: nil)
97
+ return unless Config.log_enabled?
98
+ log_entry = {
99
+ timestamp: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
100
+ action: action
101
+ }
102
+ log_entry[:target_dir] = target_dir if target_dir
103
+ log_entry[:where] = $parent_dir
104
+ logs = []
105
+ if File.exist?(Config.log_path)
106
+ begin
107
+ logs = YAML.load_file(Config.log_path) || []
108
+ rescue
109
+ logs = []
110
+ end
111
+ end
112
+ logs << log_entry
113
+ File.write(Config.log_path, logs.to_yaml)
114
+ end
115
+
116
+ # ...existing code...
117
+ ````
118
+
119
+ - これでconfig関連の処理が`Config`クラスに集約され、グローバル変数の使用も減ります。
120
+ - 他の箇所で`$conf`や`CONF_PATH`を参照していた場合は`Config.conf`や`Config::CONF_PATH`に置き換えてください。
121
+
122
+ ## User
123
+ class Configを切り離して,lib/mk_semi_lattice/config.rbに移しましょう.
124
+
125
+ ## Copilot
126
+ ````ruby
127
+ # frozen_string_literal: true
128
+ require 'yaml'
129
+ require 'fileutils'
130
+ require 'colorize'
131
+
132
+ class Config
133
+ CONFIG_DIR = File.expand_path("~/.config/semi_lattice")
134
+ CONF_PATH = File.join(CONFIG_DIR, "semi_lattice.conf")
135
+ LOG_PATH = File.join(CONFIG_DIR, "semi_lattice_history")
136
+
137
+ @conf = { "log" => false }
138
+
139
+ class << self
140
+ attr_reader :conf
141
+
142
+ def setup
143
+ FileUtils.mkdir_p(CONFIG_DIR)
144
+ load_conf
145
+ end
146
+
147
+ def load_conf
148
+ if File.file?(CONF_PATH)
149
+ begin
150
+ loaded = YAML.load_file(CONF_PATH)
151
+ if loaded.is_a?(Hash)
152
+ @conf.merge!(loaded)
153
+ else
154
+ puts "Warning: #{CONF_PATH} is not a hash. Using default config.".yellow
155
+ end
156
+ rescue
157
+ puts "Warning: #{CONF_PATH} is invalid. Using default config.".yellow
158
+ end
159
+ end
160
+ end
161
+
162
+ def save_conf
163
+ File.write(CONF_PATH, @conf.to_yaml)
164
+ end
165
+
166
+ def log_enabled?
167
+ @conf["log"]
168
+ end
169
+
170
+ def set_log(value)
171
+ @conf["log"] = value
172
+ save_conf
173
+ end
174
+
175
+ def log_path
176
+ LOG_PATH
177
+ end
178
+ end
179
+ end
180
+ ````
181
+
182
+ そして、mk_semi_lattice.rbの先頭付近で次のように修正してください。
183
+
184
+ ````ruby
185
+ # ...existing code...
186
+ require_relative "mk_semi_lattice/config"
187
+ # ...class Configの定義を削除...
188
+ Config.setup
189
+ # ...existing code...
190
+ ````
191
+
192
+ これで`Config`クラスが分離され、他のファイルからも`require_relative "mk_semi_lattice/config"`で利用できます。
193
+
194
+ ## User
195
+ #file:mk_semi_lattice.rb:12-69 あたりも修正を反映させて.
196
+
197
+ ## Copilot
198
+ ````ruby
199
+ # ...existing code...
200
+ require_relative "mk_semi_lattice/version"
201
+ require_relative "mk_semi_lattice/mk_dir_yaml"
202
+ require_relative "mk_semi_lattice/mk_node_edge"
203
+ require_relative "mk_semi_lattice/mk_semi_lattice_graph"
204
+ require_relative "mk_semi_lattice/config" # ← 追加
205
+
206
+ $semi_dir = ''
207
+ class Error < StandardError; end
208
+
209
+ puts "mk_semi_lattice is running..."
210
+
211
+ # ↓↓↓ ここ(class Config ... end)は削除してください ↓↓↓
212
+ # class Config
213
+ # CONFIG_DIR = File.expand_path("~/.config/semi_lattice")
214
+ # CONF_PATH = File.join(CONFIG_DIR, "semi_lattice.conf")
215
+ # LOG_PATH = File.join(CONFIG_DIR, "semi_lattice_history")
216
+ # ...
217
+ # end
218
+
219
+ Config.setup
220
+ # ...existing code...
221
+ ````
222
+
223
+ - `class Config ... end` の定義を削除し、
224
+ - 代わりに `require_relative "mk_semi_lattice/config"` を追加してください。
225
+ - 以降の `Config.setup` や `Config.xxx` の呼び出しはそのまま使えます。
226
+
227
+ この修正で、`Config` クラスは config.rb から読み込まれるようになります。
228
+
229
+ ## User
230
+ #file:mk_semi_lattice.rb:72-96 を分離してclass Logを作成
231
+
232
+ ## Copilot
233
+ ````ruby
234
+ # frozen_string_literal: true
235
+ require 'yaml'
236
+
237
+ class Log
238
+ class << self
239
+ def event(action, target_dir: nil, parent_dir: Dir.pwd)
240
+ return unless Config.log_enabled?
241
+ log_entry = {
242
+ timestamp: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
243
+ action: action
244
+ }
245
+ log_entry[:target_dir] = target_dir if target_dir
246
+ log_entry[:where] = parent_dir
247
+ logs = []
248
+ if File.exist?(Config.log_path)
249
+ begin
250
+ logs = YAML.load_file(Config.log_path) || []
251
+ rescue
252
+ logs = []
253
+ end
254
+ end
255
+ logs << log_entry
256
+ File.write(Config.log_path, logs.to_yaml)
257
+ end
258
+ end
259
+ end
260
+ ````
261
+
262
+ **使い方(lib/mk_semi_lattice.rb側の修正例):**
263
+
264
+ ````ruby
265
+ # ...existing code...
266
+ require_relative "mk_semi_lattice/log"
267
+
268
+ # ...existing code...
269
+
270
+ Log.event("started", parent_dir: $parent_dir)
271
+
272
+ # ...log_event("open", ...) → Log.event("open", target_dir: ..., parent_dir: $parent_dir)
273
+ # ...log_event("exited") → Log.event("exited", parent_dir: $parent_dir)
274
+ ````
275
+
276
+ - これでログ機能が`Log`クラスに分離され、`Log.event`で呼び出せます。
277
+ - `$parent_dir`は必要に応じて引数で渡してください(デフォルトは`Dir.pwd`)。
278
+
279
+ ## User
280
+ #file:mk_semi_lattice.rb:72-96 を変更を反映.
281
+
282
+ ## Copilot
283
+ ````ruby
284
+ # ...existing code...
285
+ require_relative "mk_semi_lattice/log"
286
+ # ...existing code...
287
+
288
+ # LOG_DIR, LOG_PATH, log_eventメソッドを削除
289
+
290
+ Log.event("started", parent_dir: $parent_dir)
291
+ # ...existing code...
292
+ ````
293
+
294
+ **新規ファイル:**
295
+
296
+ ````ruby
297
+ # frozen_string_literal: true
298
+ require 'yaml'
299
+
300
+ class Log
301
+ class << self
302
+ def event(action, target_dir: nil, parent_dir: Dir.pwd)
303
+ return unless Config.log_enabled?
304
+ log_entry = {
305
+ timestamp: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
306
+ action: action
307
+ }
308
+ log_entry[:target_dir] = target_dir if target_dir
309
+ log_entry[:where] = parent_dir
310
+ logs = []
311
+ if File.exist?(Config.log_path)
312
+ begin
313
+ logs = YAML.load_file(Config.log_path) || []
314
+ rescue
315
+ logs = []
316
+ end
317
+ end
318
+ logs << log_entry
319
+ File.write(Config.log_path, logs.to_yaml)
320
+ end
321
+ end
322
+ end
323
+ ````
324
+
325
+ **ポイント:**
326
+ - `log_event`の呼び出しはすべて`Log.event`に置き換えてください(例: `Log.event("open", target_dir: ..., parent_dir: $parent_dir)`)。
327
+ - `LOG_DIR`や`LOG_PATH`の定義も不要です(`Config`クラスで管理)。
328
+ - `require_relative "mk_semi_lattice/log"`を忘れずに追加してください。
329
+
330
+ ## User
331
+ /Users/bob/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/mk_semi_lattice-0.1.0/lib/mk_semi_lattice.rb:261:in `block in <top (required)>': undefined method `log_event' for main:Object (NoMethodError)
332
+ /Users/bob/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/mk_semi_lattice-0.1.0/lib/mk_semi_lattice.rb:169:in `double_click_action': undefined method `log_event' for main:Object (NoMethodError)
333
+ となっています.
334
+
335
+ ## Copilot
336
+ `log_event`メソッドを`Log.event`に置き換える必要があります。
337
+ `log_event(...)`を呼んでいる箇所をすべて`Log.event(...)`に修正してください。
338
+
339
+ 例:
340
+
341
+ ````ruby
342
+ # ...existing code...
343
+
344
+ # 例: 起動時
345
+ Log.event("started", parent_dir: $parent_dir)
346
+
347
+ # 例: ダブルクリックアクション内
348
+ Log.event("open", target_dir: File.expand_path(ファイルパス, $parent_dir), parent_dir: $parent_dir)
349
+
350
+ # 例: 終了時
351
+ Log.event("exited", parent_dir: $parent_dir)
352
+ # ...existing code...
353
+ ````
354
+
355
+ **ポイント**
356
+ - `log_event`というメソッドはもう存在しません。
357
+ - 代わりに`Log.event`を使ってください。
358
+ - `require_relative "mk_semi_lattice/log"`がファイル冒頭に必要です。
359
+
360
+ **例: ダブルクリックアクションの修正例**
361
+ ````ruby
362
+ # ...existing code...
363
+ def double_click_action(clicked_node)
364
+ comm = nil
365
+ if clicked_node.file_path
366
+ # ...省略...
367
+ Log.event("open", target_dir: File.expand_path(clicked_node.file_path, $parent_dir), parent_dir: $parent_dir)
368
+ system comm
369
+ else
370
+ puts "no link error"
371
+ end
372
+ end
373
+ # ...existing code...
374
+ ````
375
+
376
+ この修正でエラーは解消します。
377
+
378
+ ## User
379
+ /Users/bob/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/mk_semi_lattice-0.1.0/lib/mk_semi_lattice.rb:72:in `<top (required)>': uninitialized constant CONFIG_DIR (NameError)
380
+ Did you mean? Config
381
+ とエラーが出てます.
382
+
383
+ ## Copilot
384
+ `CONFIG_DIR`や`LOG_PATH`などの定数は、すでに`Config`クラスで管理されています。
385
+ mk_semi_lattice.rb内の以下の部分は不要なので削除してください。
386
+
387
+ ```ruby
388
+ LOG_DIR = CONFIG_DIR
389
+ Dir.mkdir_p(LOG_DIR) unless Dir.exist?(LOG_DIR)
390
+ LOG_PATH = File.join(LOG_DIR, "semi_lattice_history") # 拡張子なし
391
+
392
+ def log_event(action, target_dir: nil)
393
+ # ...省略...
394
+ end
395
+ ```
396
+
397
+ また、`log_event(...)`の呼び出しもすべて削除し、`Log.event(...)`のみを使ってください。
398
+
399
+ **修正例:**
400
+
401
+ ````ruby
402
+ # ...既存のrequireの下に追加...
403
+ require_relative "mk_semi_lattice/log"
404
+ # ...既存コード...
405
+
406
+ # ↓↓↓ この部分を削除してください ↓↓↓
407
+ # LOG_DIR = CONFIG_DIR
408
+ # Dir.mkdir_p(LOG_DIR) unless Dir.exist?(LOG_DIR)
409
+ # LOG_PATH = File.join(LOG_DIR, "semi_lattice_history") # 拡張子なし
410
+ #
411
+ # def log_event(action, target_dir: nil)
412
+ # ...
413
+ # end
414
+
415
+ # ...既存コード...
416
+
417
+ # 例: 起動時
418
+ Log.event("started", parent_dir: $parent_dir)
419
+
420
+ # log_event("started") ← これも削除
421
+
422
+ # ...ダブルクリックアクション内...
423
+ Log.event("open", target_dir: File.expand_path(clicked_node.file_path, $parent_dir), parent_dir: $parent_dir)
424
+ # log_event("open", ...) ← これも削除
425
+
426
+ # ...終了時...
427
+ Log.event("exited", parent_dir: $parent_dir)
428
+ # log_event("exited") ← これも削除
429
+
430
+ # ...既存コード...
431
+ ````
432
+
433
+ **ポイント**
434
+ - `CONFIG_DIR`や`LOG_PATH`は`Config`クラスで管理されているので、直接参照しないでください。
435
+ - ログ記録は`Log.event`のみを使ってください。
436
+ - `require_relative "mk_semi_lattice/log"`を忘れずに。
437
+
438
+ この修正で`uninitialized constant CONFIG_DIR`エラーは解消します。
439
+
440
+ ## User
441
+ #selectionを管理するclass OptionManagerを作成して分離.
442
+
443
+ ## Copilot
444
+ ````ruby
445
+ # frozen_string_literal: true
446
+ require 'optparse'
447
+ require 'colorize'
448
+
449
+ class OptionManager
450
+ attr_reader :options
451
+
452
+ def initialize
453
+ @options = { layer: 2, init_step: :from_semi_lattice, show_index: false, merge: false }
454
+ end
455
+
456
+ def parse!
457
+ OptionParser.new do |opts|
458
+ opts.banner = "Usage: mk_semi_lattice PATH [-L layer] [-t FILE] [-n FILE]\n default PATH = '.'"
459
+
460
+ opts.on("-L N", Integer, "Layer depth (default: 2)") do |v|
461
+ @options[:layer] = v
462
+ end
463
+
464
+ opts.on("-n", "--node=FILE", "using File from node-edge") do |file|
465
+ @options[:file] = file
466
+ @options[:init_step] = :from_node_edge
467
+ end
468
+
469
+ opts.on("-t", "--tree=FILE", "using File from tree") do |file|
470
+ @options[:file] = file
471
+ @options[:init_step] = :from_tree
472
+ end
473
+
474
+ opts.on("-i", "--index", "Display node ids") do
475
+ @options[:show_index] = true
476
+ end
477
+
478
+ opts.on("-l", "--log [BOOL]", "Enable/disable logging (true/false), and save to config") do |v|
479
+ bool =
480
+ if v.nil?
481
+ true
482
+ elsif v.is_a?(String)
483
+ case v.strip.downcase
484
+ when "true", "yes", "on", "1"
485
+ true
486
+ when "false", "no", "off", "0"
487
+ false
488
+ else
489
+ puts "Invalid value for log: #{v}. Using default: false".yellow
490
+ false
491
+ end
492
+ else
493
+ !!v
494
+ end
495
+ Config.set_log(bool)
496
+ puts "Logging is now #{bool ? 'enabled' : 'disabled'} (saved to #{Config::CONF_PATH})"
497
+ exit
498
+ end
499
+ end.parse!
500
+ @options
501
+ end
502
+ end
503
+ ````
504
+
505
+ **mk_semi_lattice.rb 側の修正例:**
506
+
507
+ ````ruby
508
+ require_relative "mk_semi_lattice/option_manager"
509
+ # ...existing code...
510
+
511
+ option_manager = OptionManager.new
512
+ options = option_manager.parse!
513
+
514
+ # 以降は options をそのまま利用
515
+ ````
516
+
517
+ このようにすることで、コマンドラインオプションの管理が`OptionManager`クラスに集約され、mk_semi_lattice.rbがすっきりします。
518
+
519
+ ## User
520
+ #file:mk_semi_lattice.rb:32-51 はLog.eventに代替しませんでしたっけ?
521
+
522
+ ## Copilot
523
+ はい、その通りです。
524
+ `log_event`メソッド(#file:mk_semi_lattice.rb:32-51)は**不要**です。
525
+ すでに`Log.event`で代替されているので、**このメソッド定義は削除してください**。
526
+
527
+ 修正例:
528
+
529
+ ````ruby
530
+ # ...existing code...
531
+
532
+ # ↓↓↓ この部分を削除してください ↓↓↓
533
+ # def log_event(action, target_dir: nil)
534
+ # return unless Config.log_enabled?
535
+ # log_entry = {
536
+ # timestamp: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
537
+ # action: action
538
+ # }
539
+ # log_entry[:target_dir] = target_dir if target_dir
540
+ # log_entry[:where] = $parent_dir
541
+ # logs = []
542
+ # if File.exist?(Config.log_path)
543
+ # begin
544
+ # logs = YAML.load_file(Config.log_path) || []
545
+ # rescue
546
+ # logs = []
547
+ # end
548
+ # end
549
+ # logs << log_entry
550
+ # File.write(Config.log_path, logs.to_yaml)
551
+ # end
552
+
553
+ # ...existing code...
554
+ ````
555
+
556
+ 以降は`Log.event(...)`のみを使ってください。
557
+
558
+ ## User
559
+ #file:mk_semi_lattice.rb:35-74 の動作をまとめてclass SelectYamlを作成
560
+
561
+ ## Copilot
562
+ ````ruby
563
+ # frozen_string_literal: true
564
+
565
+ module MkSemiLattice
566
+ class SelectYaml
567
+ def initialize(parent_dir:, semi_dir:, semi_lattice_yaml_path:, options:)
568
+ @parent_dir = parent_dir
569
+ @semi_dir = semi_dir
570
+ @semi_lattice_yaml_path = semi_lattice_yaml_path
571
+ @options = options
572
+ end
573
+
574
+ # ARGV[0]とoptionsから初期ファイル・初期ステップを決定
575
+ def select_init_file_and_step
576
+ if (ARGV[0] == '.' || ARGV[0].nil?) && !@options[:file]
577
+ if File.exist?(@semi_lattice_yaml_path)
578
+ [@semi_lattice_yaml_path, :from_semi_lattice]
579
+ else
580
+ ['.', :from_dir]
581
+ end
582
+ else
583
+ [ARGV[0], @options[:init_step]]
584
+ end
585
+ end
586
+
587
+ # 初期ステップに応じて入力ファイルとwith_semi_lattice_yamlを決定
588
+ def select_input_path_and_flag(init_file, init_step)
589
+ case init_step
590
+ when :from_dir
591
+ Dir.mkdir(@semi_dir) unless Dir.exist?(@semi_dir)
592
+ in_path = init_file
593
+ out_path = File.join(@semi_dir, 'dir_tree.yaml')
594
+ MkSemiLattice::MkDirYaml.new(path: in_path, layer: @options[:layer], output_file: out_path)
595
+ in_path = out_path
596
+ out_path = File.join(@semi_dir, 'dir_node_edge.yaml')
597
+ MkSemiLattice::MkNodeEdge.new(input_path: in_path, output_path: out_path)
598
+ [out_path, false]
599
+ when :from_tree
600
+ init_file = @options[:file]
601
+ base = File.basename(init_file, File.extname(init_file))
602
+ in_path = init_file
603
+ out_path = File.join(@parent_dir, "#{base}_node_edge.yaml")
604
+ MkSemiLattice::MkNodeEdge.new(input_path: in_path, output_path: out_path)
605
+ [out_path, false]
606
+ when :from_node_edge
607
+ if File.exist?(File.join(@parent_dir, 'semi_lattice.yaml'))
608
+ puts "Warning: semi_lattice.yaml already exists in current directory.".yellow
609
+ exit 1
610
+ end
611
+ [@options[:file], false]
612
+ when :from_semi_lattice
613
+ [init_file, true]
614
+ else
615
+ raise "Unknown init_step: #{init_step}"
616
+ end
617
+ end
618
+ end
619
+ end
620
+ ````
621
+
622
+ **mk_semi_lattice.rb 側の利用例:**
623
+
624
+ ````ruby
625
+ require_relative "mk_semi_lattice/select_yaml"
626
+ # ...existing code...
627
+
628
+ selector = MkSemiLattice::SelectYaml.new(
629
+ parent_dir: $parent_dir,
630
+ semi_dir: semi_dir,
631
+ semi_lattice_yaml_path: semi_lattice_yaml_path,
632
+ options: options
633
+ )
634
+ init_file, init_step = selector.select_init_file_and_step
635
+ p [init_file, init_step]
636
+ input_path, with_semi_lattice_yaml = selector.select_input_path_and_flag(init_file, init_step)
637
+ p [input_path, with_semi_lattice_yaml]
638
+
639
+ # ...existing code...
640
+ ````
641
+
642
+ このように分離することで、初期ファイル・初期ステップの選択ロジックが`SelectYaml`クラスにまとまります。
643
+
644
+ ## User
645
+ #file:mk_semi_lattice.rb:160-192 の動作を行う関数def at_exit_actionを作成.
646
+
647
+ ## Copilot
648
+ ````ruby
649
+ # ...existing code...
650
+
651
+ def at_exit_action(app, semi_dir, parent_dir)
652
+ nodes_data = app.nodes.map do |n|
653
+ p [n.label, n.fixed, n.color]
654
+ {
655
+ id: app.node_table.key(n),
656
+ name: n.name,
657
+ type: n.type,
658
+ file_path: n.file_path,
659
+ x: n.x,
660
+ y: n.y,
661
+ color: n.color,
662
+ fixed: n.fixed
663
+ }
664
+ end
665
+ edges_data = app.edges.map do |e|
666
+ {
667
+ from: app.node_table.key(e.from),
668
+ to: app.node_table.key(e.to)
669
+ }
670
+ end
671
+
672
+ yaml_data = { nodes: nodes_data, edges: edges_data }
673
+ yaml_text = MkSemiLattice::MkNodeEdge.add_edge_comments(yaml_data)
674
+ if Dir.exist?(semi_dir)
675
+ File.write(File.join(semi_dir, "semi_lattice.yaml"), yaml_text)
676
+ else
677
+ File.write(File.join('.', "semi_lattice.yaml"), yaml_text)
678
+ end
679
+ Log.event("exited", parent_dir: parent_dir)
680
+ end
681
+
682
+ # ...existing code...
683
+
684
+ at_exit do
685
+ at_exit_action(app, semi_dir, $parent_dir)
686
+ end
687
+
688
+ # ...existing code...
689
+ ````
690
+
691
+ このように関数化することで、`at_exit`ブロックがすっきりします。
692
+
693
+ ## User
694
+ at_exit_actionをSelectYamlに入れることは可能ですか?
695
+
696
+ ## Copilot
697
+ はい、`at_exit_action`を`SelectYaml`に入れることは**技術的には可能**ですが、設計上はあまりおすすめしません。
698
+
699
+ ### 理由
700
+ - `SelectYaml`は「初期ファイルや初期ステップの選択」を担当するクラスです。
701
+ - `at_exit_action`は「アプリの状態(ノード・エッジ)をYAMLに保存する」責務であり、役割が異なります。
702
+ - 責務分離の観点から、`at_exit_action`は`SelectYaml`ではなく、例えば`MkSemiLatticeData`のインスタンスメソッドや、専用の`Saver`クラスなどに分離するのが自然です。
703
+
704
+ ---
705
+
706
+ ### どうしても`SelectYaml`に入れたい場合
707
+
708
+ - クラスメソッドやインスタンスメソッドとして追加はできます。
709
+ - ただし、`SelectYaml`の責務が曖昧になり、保守性が下がります。
710
+
711
+ #### 例(クラスメソッドとして追加):
712
+
713
+ ````ruby
714
+ module MkSemiLattice
715
+ class SelectYaml
716
+ # ...既存コード...
717
+
718
+ def self.at_exit_action(app, semi_dir, parent_dir)
719
+ nodes_data = app.nodes.map do |n|
720
+ {
721
+ id: app.node_table.key(n),
722
+ name: n.name,
723
+ type: n.type,
724
+ file_path: n.file_path,
725
+ x: n.x,
726
+ y: n.y,
727
+ color: n.color,
728
+ fixed: n.fixed
729
+ }
730
+ end
731
+ edges_data = app.edges.map do |e|
732
+ {
733
+ from: app.node_table.key(e.from),
734
+ to: app.node_table.key(e.to)
735
+ }
736
+ end
737
+
738
+ yaml_data = { nodes: nodes_data, edges: edges_data }
739
+ yaml_text = MkSemiLattice::MkNodeEdge.add_edge_comments(yaml_data)
740
+ if Dir.exist?(semi_dir)
741
+ File.write(File.join(semi_dir, "semi_lattice.yaml"), yaml_text)
742
+ else
743
+ File.write(File.join('.', "semi_lattice.yaml"), yaml_text)
744
+ end
745
+ Log.event("exited", parent_dir: parent_dir)
746
+ end
747
+ end
748
+ end
749
+ ````
750
+
751
+ **呼び出し側:**
752
+
753
+ ````ruby
754
+ at_exit do
755
+ MkSemiLattice::SelectYaml.at_exit_action(app, semi_dir, $parent_dir)
756
+ end
757
+ ````
758
+
759
+ ---
760
+
761
+ ### 結論
762
+
763
+ - **可能だが、設計上は推奨しません。**
764
+ - 責務分離のため、保存処理は別クラスや`MkSemiLatticeData`のメソッドにするのがベターです。
765
+
766
+ ## User
767
+ MkSemiLatticeDataをMkSemiLatticeGraphDataに変更
768
+
769
+ ## Copilot
770
+ ````ruby
771
+ # ...existing code...
772
+ class MkSemiLatticeGraphData
773
+ attr_reader :nodes, :edges, :node_table
774
+ attr_accessor :selected, :shift_pressed, :show_index
775
+
776
+ def initialize(file = "dir_node_edge.yaml", with_semi_lattice_yaml: false, show_index: false)
777
+ @nodes = []
778
+ @edges = []
779
+ @node_table = {}
780
+ @selected = nil
781
+ @shift_pressed = false
782
+ p @show_index = show_index
783
+ load_yaml_data_with_state(file, with_semi_lattice_yaml: with_semi_lattice_yaml)
784
+ end
785
+
786
+ # ...以下同じ...
787
+ end
788
+ ````
789
+
790
+ ````ruby
791
+ # ...existing code...
792
+
793
+ app = MkSemiLatticeGraphData.new(input_path,
794
+ with_semi_lattice_yaml: with_semi_lattice_yaml,
795
+ show_index: options[:show_index]
796
+ )
797
+
798
+ # ...existing code...
799
+ ````
800
+
801
+ - `MkSemiLatticeData` → `MkSemiLatticeGraphData` にクラス名を変更してください。
802
+ - インスタンス生成も同様に変更してください。
803
+
804
+ ## User
805
+ SelectYamlをManageYamlに変更.
806
+
807
+ ## Copilot
808
+ ````ruby
809
+ # frozen_string_literal: true
810
+
811
+ module MkSemiLattice
812
+ class ManageYaml
813
+ def initialize(parent_dir:, semi_dir:, semi_lattice_yaml_path:, options:)
814
+ @parent_dir = parent_dir
815
+ @semi_dir = semi_dir
816
+ @semi_lattice_yaml_path = semi_lattice_yaml_path
817
+ @options = options
818
+ end
819
+
820
+ # ARGV[0]とoptionsから初期ファイル・初期ステップを決定
821
+ def select_init_file_and_step
822
+ if (ARGV[0] == '.' || ARGV[0].nil?) && !@options[:file]
823
+ if File.exist?(@semi_lattice_yaml_path)
824
+ [@semi_lattice_yaml_path, :from_semi_lattice]
825
+ else
826
+ ['.', :from_dir]
827
+ end
828
+ else
829
+ [ARGV[0], @options[:init_step]]
830
+ end
831
+ end
832
+
833
+ # 初期ステップに応じて入力ファイルとwith_semi_lattice_yamlを決定
834
+ def select_input_path_and_flag(init_file, init_step)
835
+ case init_step
836
+ when :from_dir
837
+ Dir.mkdir(@semi_dir) unless Dir.exist?(@semi_dir)
838
+ in_path = init_file
839
+ out_path = File.join(@semi_dir, 'dir_tree.yaml')
840
+ MkSemiLattice::MkDirYaml.new(path: in_path, layer: @options[:layer], output_file: out_path)
841
+ in_path = out_path
842
+ out_path = File.join(@semi_dir, 'dir_node_edge.yaml')
843
+ MkSemiLattice::MkNodeEdge.new(input_path: in_path, output_path: out_path)
844
+ [out_path, false]
845
+ when :from_tree
846
+ init_file = @options[:file]
847
+ base = File.basename(init_file, File.extname(init_file))
848
+ in_path = init_file
849
+ out_path = File.join(@parent_dir, "#{base}_node_edge.yaml")
850
+ MkSemiLattice::MkNodeEdge.new(input_path: in_path, output_path: out_path)
851
+ [out_path, false]
852
+ when :from_node_edge
853
+ if File.exist?(File.join(@parent_dir, 'semi_lattice.yaml'))
854
+ puts "Warning: semi_lattice.yaml already exists in current directory.".yellow
855
+ exit 1
856
+ end
857
+ [@options[:file], false]
858
+ when :from_semi_lattice
859
+ [init_file, true]
860
+ else
861
+ raise "Unknown init_step: #{init_step}"
862
+ end
863
+ end
864
+ end
865
+ end
866
+ ````
867
+
868
+ **呼び出し側(mk_semi_lattice.rb)も修正してください:**
869
+
870
+ ````ruby
871
+ require_relative "mk_semi_lattice/manage_yaml" # ファイル名も変更した場合
872
+
873
+ # ...
874
+ selector = MkSemiLattice::ManageYaml.new(
875
+ parent_dir: $parent_dir,
876
+ semi_dir: semi_dir,
877
+ semi_lattice_yaml_path: semi_lattice_yaml_path,
878
+ options: options
879
+ )
880
+ init_file, init_step = selector.select_init_file_and_step
881
+ input_path, with_semi_lattice_yaml = selector.select_input_path_and_flag(init_file, init_step)
882
+ # ...
883
+ ````
884
+
885
+ - クラス名を`SelectYaml`→`ManageYaml`に変更
886
+ - ファイル名もselect_yaml.rb→`manage_yaml.rb`に変更するのが推奨です(`require_relative`も合わせて修正)
887
+
888
+ この修正で意図通り動作します。
889
+
890
+ ## User
891
+ def at_exit_action(app, semi_dir, parent_dir)をManageYamlに移動.
892
+
893
+ ## Copilot
894
+ ````ruby
895
+ # ...existing code...
896
+
897
+ # アプリ終了時の状態保存
898
+ def self.at_exit_action(app, semi_dir, parent_dir)
899
+ nodes_data = app.nodes.map do |n|
900
+ p [n.label, n.fixed, n.color]
901
+ {
902
+ id: app.node_table.key(n),
903
+ name: n.name,
904
+ type: n.type,
905
+ file_path: n.file_path,
906
+ x: n.x,
907
+ y: n.y,
908
+ color: n.color,
909
+ fixed: n.fixed
910
+ }
911
+ end
912
+ edges_data = app.edges.map do |e|
913
+ {
914
+ from: app.node_table.key(e.from),
915
+ to: app.node_table.key(e.to)
916
+ }
917
+ end
918
+
919
+ yaml_data = { nodes: nodes_data, edges: edges_data }
920
+ yaml_text = MkSemiLattice::MkNodeEdge.add_edge_comments(yaml_data)
921
+ if Dir.exist?(semi_dir)
922
+ File.write(File.join(semi_dir, "semi_lattice.yaml"), yaml_text)
923
+ else
924
+ File.write(File.join('.', "semi_lattice.yaml"), yaml_text)
925
+ end
926
+ Log.event("exited", parent_dir: parent_dir)
927
+ end
928
+
929
+ # ...existing code...
930
+ ````
931
+
932
+ ````ruby
933
+ # ...existing code...
934
+
935
+ # Ruby2Dには:closeイベントはありません。at_exitで保存処理を行います。
936
+ at_exit do
937
+ MkSemiLattice::ManageYaml.at_exit_action(app, semi_dir, $parent_dir)
938
+ end
939
+
940
+ # ...existing code...
941
+ ````
942
+
943
+ - `at_exit_action`は`ManageYaml`の**クラスメソッド**として追加しました。
944
+ - `at_exit`ブロックでは`MkSemiLattice::ManageYaml.at_exit_action(...)`で呼び出してください。
945
+ - 元の`def at_exit_action ... end`は削除してください。
946
+
947
+ ## User
948
+ それぞれの動作に対するactionをそれぞれの関数を作成し,それらをlib/ruby2d_action.rbに移動.
949
+
950
+ ## Copilot
951
+ ````ruby
952
+ # frozen_string_literal: true
953
+
954
+ module Ruby2dAction
955
+ def self.on_key_down(app, event)
956
+ app.shift_pressed = true if event.key.include?('shift')
957
+ end
958
+
959
+ def self.on_key_up(app, event)
960
+ app.shift_pressed = false if event.key.include?('shift')
961
+ end
962
+
963
+ def self.double_click?(clicked_node, last_click_node, last_click_time)
964
+ now = Time.now
965
+ if last_click_node == clicked_node && last_click_time && (now - last_click_time < 0.4)
966
+ return true, now
967
+ end
968
+ return false, now
969
+ end
970
+
971
+ def self.double_click_action(clicked_node, parent_dir)
972
+ comm = nil
973
+ if clicked_node.file_path
974
+ if File.directory?(clicked_node.file_path)
975
+ if RbConfig::CONFIG['host_os'] =~ /darwin/
976
+ comm = "open -a Terminal '#{clicked_node.file_path}'"
977
+ elsif RbConfig::CONFIG['host_os'] =~ /debian/
978
+ comm = "gnome-terminal --working-directory='#{clicked_node.file_path}'"
979
+ else
980
+ comm = "wt.exe -p Ubuntu-24.04 --colorScheme 'Tango Light' -d '#{clicked_node.file_path}'"
981
+ end
982
+ else
983
+ if RbConfig::CONFIG['host_os'] =~ /darwin/
984
+ comm = "open '#{clicked_node.file_path}'"
985
+ else
986
+ comm = "open '#{clicked_node.file_path}'"
987
+ end
988
+ end
989
+ puts comm
990
+ Log.event("open", target_dir: File.expand_path(clicked_node.file_path, parent_dir), parent_dir: parent_dir)
991
+ system comm
992
+ else
993
+ puts "no link error"
994
+ end
995
+ end
996
+
997
+ def self.on_mouse_down(app, event, last_click_node, last_click_time, parent_dir)
998
+ mx, my = event.x, event.y
999
+ shift_down = !!app.shift_pressed
1000
+ clicked_node = nil
1001
+ app.nodes.each do |n|
1002
+ if Math.hypot(n.x - mx, n.y - my) < 30
1003
+ clicked_node = n
1004
+ if shift_down
1005
+ n.fixed = false
1006
+ n.color = NODE_COLOR
1007
+ app.selected = nil
1008
+ else
1009
+ app.selected = n
1010
+ if event.button == :left
1011
+ n.fixed = true
1012
+ n.color = FIXED_COLOR
1013
+ end
1014
+ n.fixed = false if event.button == :middle
1015
+ n.linked = true if event.button == :right
1016
+ end
1017
+ end
1018
+ end
1019
+
1020
+ # ダブルクリック判定とファイルオープン
1021
+ if clicked_node
1022
+ is_double, now = double_click?(clicked_node, last_click_node, last_click_time)
1023
+ double_click_action(clicked_node, parent_dir) if is_double
1024
+ return clicked_node, now
1025
+ end
1026
+ [last_click_node, last_click_time]
1027
+ end
1028
+
1029
+ def self.on_mouse_up(app)
1030
+ app.selected = nil
1031
+ end
1032
+
1033
+ def self.on_mouse_move(app, event)
1034
+ if app.selected
1035
+ app.selected.x = event.x
1036
+ app.selected.y = event.y
1037
+ end
1038
+ end
1039
+
1040
+ def self.update_action(app)
1041
+ app.edges.each(&:relax)
1042
+ app.nodes.each { |n| n.relax(app.nodes) }
1043
+ app.nodes.each(&:update)
1044
+ app.edges.reverse.each(&:draw)
1045
+ app.nodes.reverse.each do |n|
1046
+ n.draw(app.selected == n)
1047
+ end
1048
+ end
1049
+ end
1050
+ ````
1051
+
1052
+ **mk_semi_lattice.rb 側の修正例:**
1053
+
1054
+ ````ruby
1055
+ require_relative "ruby2d_action"
1056
+ # ...existing code...
1057
+
1058
+ last_click_time = nil
1059
+ last_click_node = nil
1060
+
1061
+ on :key_down do |event|
1062
+ Ruby2dAction.on_key_down(app, event)
1063
+ end
1064
+
1065
+ on :key_up do |event|
1066
+ Ruby2dAction.on_key_up(app, event)
1067
+ end
1068
+
1069
+ on :mouse_down do |event|
1070
+ clicked_node, last_time = Ruby2dAction.on_mouse_down(app, event, last_click_node, last_click_time, $parent_dir)
1071
+ last_click_node = clicked_node
1072
+ last_click_time = last_time
1073
+ end
1074
+
1075
+ on :mouse_up do
1076
+ Ruby2dAction.on_mouse_up(app)
1077
+ end
1078
+
1079
+ on :mouse_move do |event|
1080
+ Ruby2dAction.on_mouse_move(app, event)
1081
+ end
1082
+
1083
+ update do
1084
+ Ruby2dAction.update_action(app)
1085
+ end
1086
+ ````
1087
+
1088
+ - それぞれのRuby2Dイベントの処理を`Ruby2dAction`モジュールの関数に分離しました。
1089
+ - `last_click_node`と`last_click_time`の更新も`on_mouse_down`の返り値で管理できます。
1090
+ - これでmk_semi_lattice.rbがすっきりし、イベントごとの処理が明確になります。