cc 1.1.5 → 1.2.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 (5) hide show
  1. checksums.yaml +4 -4
  2. data/cc.rb +2 -0
  3. data/shell-tools.rb +264 -0
  4. data/trace.rb +327 -0
  5. metadata +3 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f4f552fd0a586d4321f95a788b210de1ae859f187f03ff92dff524c16021d49
4
- data.tar.gz: 857478152baa7bc3ced3d103f8f8eb6797989563d7242e25dc7b7e1564b98be2
3
+ metadata.gz: 4fc0484d6d2387f8043520b7981b52be8968f1a233cf5e110395a2dcc9ab5fa8
4
+ data.tar.gz: 88f19e9a08e517ccdebb8d9f5d8dbb8afec0dd675d6b0c6df057c40703d75993
5
5
  SHA512:
6
- metadata.gz: de7cb9814ffcc3b8acae9c5ad48b5a46e61d7524a28c4cd44ae7a8bd6de7ae8374bfb19474cd0cfcd7174e399c11acf1867a3bd48fb959f526bdf6cc113a8e9b
7
- data.tar.gz: 81d1bcb0123efe87e9a037364cfc4da673ccf6804b0bcadab97f4da30d9385ac3c9035c5f61a65894169bde84aad2487c8f3f47e87c3c47d690714d5b2d349bf
6
+ metadata.gz: 371bdf3d14c78623c029bd58aff0002383f256dc9346ab5f6586b0ad7920690c9698338d1d73ccbffcab07019d0c9a132ced0365d6f7198c3c7040371329900e
7
+ data.tar.gz: 874c41883cb25ddf44790b37d8b7589ef5ff87bb07e25d5d7908f2553dedb3b9cc96af751a7886902839e3e7084931cdd1a30b547c8c6c43a63721bf0b4af6b8
data/cc.rb CHANGED
@@ -10,10 +10,12 @@ $modlist = [
10
10
  'file',
11
11
  'kernel',
12
12
  'monkey-patch',
13
+ 'shell-tools',
13
14
  'number',
14
15
  'regexp',
15
16
  'string',
16
17
  'tree',
18
+ 'trace',
17
19
  'logic'
18
20
  ]
19
21
 
data/shell-tools.rb ADDED
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require 'io/console'
5
+
6
+ class ProgressBar
7
+ # 预设主题
8
+ THEMES = {
9
+ classic: { fill: '█', empty: '░', left: '[', right: ']', arrow: '>' },
10
+ modern: { fill: '●', empty: '○', left: '', right: '', arrow: '▶' },
11
+ blocks: { fill: '■', empty: '□', left: '│', right: '│', arrow: '▶' },
12
+ dots: { fill: '◉', empty: '◎', left: '⟨', right: '⟩', arrow: '→' },
13
+ minimal: { fill: '=', empty: '-', left: '[', right: ']', arrow: '>' },
14
+ hearts: { fill: '♥', empty: '♡', left: ' ', right: ' ', arrow: '💕' }
15
+ }
16
+
17
+ attr_reader :total, :current, :start_time
18
+
19
+ def initialize(total, title: "Processing", theme: :modern, width: 40, show_percentage: true, show_eta: true, color: true)
20
+ @total = total
21
+ @current = 0
22
+ @title = title
23
+ @theme = THEMES[theme] || THEMES[:modern]
24
+ @width = width
25
+ @show_percentage = show_percentage
26
+ @show_eta = show_eta
27
+ @color = color
28
+ @start_time = Time.now
29
+ @last_update = 0
30
+ @mutex = Mutex.new
31
+
32
+ # ANSI 颜色代码
33
+ @colors = {
34
+ reset: "\e[0m",
35
+ bold: "\e[1m",
36
+ green: "\e[32m",
37
+ yellow: "\e[33m",
38
+ blue: "\e[34m",
39
+ magenta: "\e[35m",
40
+ cyan: "\e[36m",
41
+ gray: "\e[90m"
42
+ }
43
+
44
+ # 隐藏光标
45
+ print "\e[?25l"
46
+ end
47
+
48
+ # 更新进度
49
+ def update(step = 1)
50
+ @mutex.synchronize do
51
+ @current += step
52
+ @current = @total if @current > @total
53
+ refresh if should_refresh?
54
+ end
55
+ end
56
+
57
+ # 直接设置进度
58
+ def set(value)
59
+ @mutex.synchronize do
60
+ @current = [[value, @total].min, 0].max
61
+ refresh
62
+ end
63
+ end
64
+
65
+ # 完成进度条
66
+ def finish(message: "Done!")
67
+ @mutex.synchronize do
68
+ @current = @total
69
+ refresh
70
+ puts # 换行
71
+ show_cursor
72
+ puts colorize(message, :green) unless message.empty?
73
+ end
74
+ end
75
+
76
+ # 显示统计信息
77
+ def stats
78
+ elapsed = Time.now - @start_time
79
+ rate = @current / elapsed rescue 0
80
+ {
81
+ total: @total,
82
+ current: @current,
83
+ percentage: (@current.to_f / @total * 100).round(1),
84
+ elapsed: format_time(elapsed),
85
+ eta: format_time(eta),
86
+ rate: rate.round(2)
87
+ }
88
+ end
89
+
90
+ # 清理资源
91
+ def dispose
92
+ show_cursor
93
+ end
94
+
95
+ private
96
+
97
+ def should_refresh?
98
+ now = Time.now.to_f
99
+ return false if now - @last_update < 0.05 # 限制刷新率 20fps
100
+ @last_update = now
101
+ true
102
+ end
103
+
104
+ def refresh
105
+ percentage = @current.to_f / @total
106
+ filled = (percentage * @width).round
107
+ empty = @width - filled
108
+
109
+ bar = @theme[:fill] * filled +
110
+ (@current < @total ? @theme[:arrow] : @theme[:fill]) +
111
+ @theme[:empty] * [empty - 1, 0].max
112
+
113
+ # 构建输出字符串
114
+ parts = []
115
+
116
+ # 标题
117
+ parts << colorize(sprintf("%-15s", @title), :bold)
118
+
119
+ # 进度条
120
+ bar_color = percentage >= 1.0 ? :green : (percentage > 0.5 ? :cyan : :yellow)
121
+ parts << "#{@theme[:left]}#{colorize(bar, bar_color)}#{@theme[:right]}"
122
+
123
+ # 百分比
124
+ if @show_percentage
125
+ parts << colorize(sprintf("%6.1f%%", percentage * 100), :magenta)
126
+ end
127
+
128
+ # 计数
129
+ parts << colorize("[#{@current}/#{@total}]", :gray)
130
+
131
+ # ETA
132
+ if @show_eta && @current > 0 && @current < @total
133
+ parts << colorize("ETA: #{format_time(eta)}", :blue)
134
+ end
135
+
136
+ # 清除行并输出
137
+ print "\r\e[K" + parts.join(' ')
138
+ $stdout.flush
139
+ end
140
+
141
+ def eta
142
+ return 0 if @current == 0
143
+ elapsed = Time.now - @start_time
144
+ remaining = @total - @current
145
+ (elapsed / @current) * remaining
146
+ end
147
+
148
+ def format_time(seconds)
149
+ return "0s" if seconds < 1
150
+ if seconds < 60
151
+ "#{seconds.round}s"
152
+ elsif seconds < 3600
153
+ "#{(seconds / 60).floor}m #{(seconds % 60).round}s"
154
+ else
155
+ "#{(seconds / 3600).floor}h #{((seconds % 3600) / 60).floor}m"
156
+ end
157
+ end
158
+
159
+ def colorize(text, color)
160
+ return text unless @color
161
+ "#{@colors[color]}#{text}#{@colors[:reset]}"
162
+ end
163
+
164
+ def show_cursor
165
+ print "\e[?25h"
166
+ end
167
+ end
168
+
169
+ # 辅助方法(用于示例中的颜色输出)
170
+ def colorize(text, color)
171
+ colors = { green: "\e[32m", yellow: "\e[33m", gray: "\e[90m", reset: "\e[0m" }
172
+ "#{colors[color]}#{text}#{colors[:reset]}"
173
+ end
174
+
175
+ =begin
176
+ # ==================== 使用示例 ====================
177
+ puts "🚀 Ruby Fancy Progress Bar Demo"
178
+ puts "=" * 50
179
+
180
+ # 示例 1: 基础用法
181
+ puts "\n📊 示例 1: 基础文件处理"
182
+ bar = ProgressBar.new(100, title: "Downloading", theme: :modern)
183
+ 100.times do |i|
184
+ sleep(0.03) # 模拟工作
185
+ bar.update
186
+ end
187
+ bar.finish(message: "✅ 下载完成!")
188
+
189
+ # 示例 2: 不同主题展示
190
+ puts "\n🎨 示例 2: 主题展示"
191
+ [:classic, :blocks, :dots, :minimal].each do |theme_name|
192
+ bar = ProgressBar.new(50, title: theme_name.to_s, theme: theme_name, width: 30)
193
+ 50.times do
194
+ sleep(0.02)
195
+ bar.update
196
+ end
197
+ bar.finish(message: "")
198
+ end
199
+
200
+ # 示例 3: 批量任务处理(带错误处理)
201
+ puts "\n📁 示例 3: 批量文件处理"
202
+ files = (1..20).map { |n| "file_#{n}.txt" }
203
+ bar = ProgressBar.new(files.size, title: "Processing", theme: :hearts, color: true)
204
+ processed = []
205
+ files.each do |file|
206
+ sleep(0.1) # 模拟处理时间
207
+ processed << file
208
+ bar.update
209
+ # 模拟偶尔的错误
210
+ if rand < 0.1
211
+ print "\n⚠️ #{colorize("警告: #{file} 处理异常", :yellow)}"
212
+ end
213
+ end
214
+ bar.finish(message: "✨ 处理了 #{processed.size} 个文件")
215
+
216
+ # 示例 4: 嵌套进度条(多阶段任务)
217
+ puts "\n🏗️ 示例 4: 多阶段构建任务"
218
+ phases = [
219
+ { name: "Compiling", items: 30 },
220
+ { name: "Testing", items: 20 },
221
+ { name: "Packaging", items: 15 }
222
+ ]
223
+ phases.each do |phase|
224
+ puts "\n阶段: #{phase[:name]}"
225
+ bar = ProgressBar.new(phase[:items], title: phase[:name], theme: :modern)
226
+ phase[:items].times do
227
+ sleep(0.05)
228
+ bar.update
229
+ end
230
+ bar.finish(message: "")
231
+ end
232
+ puts colorize("🎉 所有阶段完成!", :green)
233
+
234
+ # 示例 5: 实时速度显示(大数据处理)
235
+ puts "\n⚡ 示例 5: 大数据处理(带实时统计)"
236
+ total_items = 1000
237
+ bar = ProgressBar.new(total_items, title: "BigData", theme: :blocks, width: 50)
238
+ total_items.times do |i|
239
+ sleep(0.01) # 模拟快速处理
240
+ # 每 100 个显示一次统计
241
+ if i % 100 == 0 && i > 0
242
+ s = bar.stats
243
+ print "\n #{colorize("速度: #{s[:rate]} items/s | 已用: #{s[:elapsed]} | 剩余: #{s[:eta]}", :gray)}"
244
+ end
245
+ bar.update
246
+ end
247
+ bar.finish
248
+
249
+ # 示例 6: 手动控制进度(非均匀任务)
250
+ puts "\n🎮 示例 6: 手动进度控制(非均匀任务)"
251
+ bar = ProgressBar.new(100, title: "Uploading", theme: :dots)
252
+ # 模拟不同大小的文件上传
253
+ uploads = [10, 25, 5, 30, 20, 10]
254
+ uploads.each_with_index do |size, idx|
255
+ sleep(0.3) # 模拟上传时间
256
+ bar.set(bar.current + size)
257
+ print "\n 上传了 chunk_#{idx + 1}.bin (#{size}MB)"
258
+ end
259
+ bar.finish(message: "上传完成")
260
+ # 确保光标显示
261
+ print "\e[?25h"
262
+ puts "\n" + "=" * 50
263
+ puts "✨ 所有演示完成! 你可以复制 ProgressBar 类到你的项目中使用。"
264
+ =end
data/trace.rb ADDED
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ # encoding: UTF-8
4
+
5
+ =begin
6
+ | 关键特性 | 说明                                |
7
+ |---------|---------------------------------------------------------------|
8
+ | 追踪功能 | TracePoint 是 Ruby 内置的追踪 API,性能比 `set_trace_func` 更好 |
9
+ | 层级缩进 | 通过 `call_depth` 显示调用层级,便于阅读 |
10
+ | 对象信息 | 记录类名、object_id、inspect 结果、集合 size 等 |
11
+ | 局部变量 | 在每一行记录当前的局部变量状态 |
12
+ | 参数追踪 | 方法调用时记录传入的参数 |
13
+ | 源码显示 | 显示当前执行的源代码行 |
14
+ | 安全截断 | 超长字符串自动截断,防止日志爆炸 |
15
+
16
+ ※注意: `trace_lines: true` 会追踪每一行代码,性能开销较大,适合调试小段代码;只追踪方法调用(默认)性能更好,适合生产环境追踪。
17
+
18
+ # 使用方法:
19
+ require 'cc'
20
+ CC.use 'trace'
21
+ ExecutionTracer.enable! # log_path: 'execution.log', trace_lines: false, max_depth: 100
22
+ hello(:world)
23
+ ExecutionTracer.disable!
24
+
25
+ # 设置环境变量调用:
26
+ `EXECUTION_TRACER_AUTO_START=true EXECUTION_TRACER_LOG=my_trace.log EXECUTION_TRACER_TRACE_LINES=true ruby 自定义脚本.rb`
27
+ =end
28
+
29
+ require 'json'
30
+ require 'date'
31
+
32
+ module ExecutionTracer
33
+ class << self
34
+ attr_reader :logger, :enabled, :log_file, :call_depth
35
+
36
+ def enable!(log_path: 'execution.log', trace_lines: false, max_depth: 100)
37
+ return if @enabled
38
+
39
+ time = Time.now
40
+ @logger = []
41
+ @logger << {
42
+ 'time' => time,
43
+ 'event' => "Execution Trace Start",
44
+ 'version' => RUBY_VERSION,
45
+ 'process-id' => Process.pid,
46
+ 'log-path' => log_path
47
+ }
48
+
49
+ @log_file = File.open(log_path, 'a')
50
+ @log_file.puts "\n" + "="*80
51
+ @log_file.puts "[#{time}] Execution Trace Start"
52
+ @log_file.puts "Ruby Version: #{RUBY_VERSION}"
53
+ @log_file.puts "Process PID: #{Process.pid}"
54
+ @log_file.puts "="*80 + "\n"
55
+ @log_file.flush
56
+
57
+ @enabled = true
58
+ @call_depth = 0
59
+ @max_depth = max_depth
60
+
61
+ setup_tracepoint(trace_lines)
62
+
63
+ puts "[ExecutionTracer] 已启用,日志写入: #{File.absolute_path(log_path)}"
64
+ end
65
+
66
+ def disable!
67
+ return unless @enabled
68
+
69
+ time = Time.now
70
+ @logger ||= []
71
+ @logger << {
72
+ 'time' => time,
73
+ 'event' => "Execution Trace End",
74
+ 'process-id' => Process.pid
75
+ }
76
+
77
+ @tracepoint&.disable
78
+ @enabled = false
79
+
80
+ @log_file.puts "\n" + "="*80
81
+ @log_file.puts "[#{time}] Execution Trace End"
82
+ @log_file.puts "="*80 + "\n"
83
+ @log_file.close
84
+
85
+ puts "[ExecutionTracer] 已禁用,日志已保存"
86
+ end
87
+
88
+ def log(message)
89
+ return unless @enabled && @log_file
90
+ @log_file.puts(message.encode("UTF-8"))
91
+ @log_file.flush
92
+ end
93
+
94
+ private
95
+
96
+ def setup_tracepoint(trace_lines)
97
+ events = [:call, :return, :c_call, :c_return]
98
+ events << :line if trace_lines
99
+
100
+ @tracepoint = TracePoint.new(*events) do |tp|
101
+ next if internal_file?(tp.path)
102
+ next if @call_depth > @max_depth
103
+
104
+ case tp.event
105
+ when :call, :c_call
106
+ @call_depth += 1
107
+ log_method_call(tp)
108
+ when :return, :c_return
109
+ log_method_return(tp)
110
+ @call_depth -= 1
111
+ when :line
112
+ log_line_execution(tp)
113
+ end
114
+ end
115
+
116
+ @tracepoint.enable
117
+ end
118
+
119
+ def log_method_call(tp)
120
+ receiver = tp.self
121
+ method_name = tp.method_id
122
+ file = tp.path
123
+ line = tp.lineno
124
+ defined_class = tp.defined_class
125
+
126
+ # 收集对象信息
127
+ object_info = extract_object_info(receiver)
128
+
129
+ # 收集参数信息(如果可能)
130
+ args_info = extract_arguments(tp.binding) if tp.binding
131
+
132
+ indent = " " * [@call_depth - 1, 0].max
133
+ log_message = format_log_entry(
134
+ event: "CALL",
135
+ indent: indent,
136
+ file: file,
137
+ line: line,
138
+ method: "#{defined_class}##{method_name}",
139
+ object: object_info,
140
+ args: args_info
141
+ )
142
+
143
+ log(log_message)
144
+ end
145
+
146
+ def log_method_return(tp)
147
+ receiver = tp.self
148
+ method_name = tp.method_id
149
+ file = tp.path
150
+ line = tp.lineno
151
+
152
+ object_info = extract_object_info(receiver)
153
+ return_value = safe_inspect(tp.return_value) if tp.respond_to?(:return_value)
154
+
155
+ indent = " " * [@call_depth - 1, 0].max
156
+ log_message = format_log_entry(
157
+ event: "RETURN",
158
+ indent: indent,
159
+ file: file,
160
+ line: line,
161
+ method: "#{tp.defined_class}##{method_name}",
162
+ object: object_info,
163
+ return: return_value
164
+ )
165
+
166
+ log(log_message)
167
+ end
168
+
169
+ def log_line_execution(tp)
170
+ file = tp.path
171
+ line = tp.lineno
172
+
173
+ # 获取当前行的代码(如果文件可读)
174
+ source_line = read_source_line(file, line)
175
+
176
+ # 获取局部变量信息
177
+ local_vars = extract_local_variables(tp.binding) if tp.binding
178
+
179
+ log_message = format_log_entry(
180
+ event: "LINE",
181
+ indent: " " * (@call_depth < 0 ? @call_depth*(-1) : @call_depth),
182
+ file: file,
183
+ line: line,
184
+ source: source_line,
185
+ locals: local_vars
186
+ )
187
+
188
+ log(log_message)
189
+ end
190
+
191
+ def extract_object_info(obj)
192
+ info = {
193
+ class: obj.class.name,
194
+ object_id: obj.object_id,
195
+ inspect: safe_inspect(obj)
196
+ }
197
+
198
+ # 如果是集合类型,记录大小
199
+ if obj.respond_to?(:size)
200
+ info[:size] = obj.size rescue "N/A"
201
+ end
202
+
203
+ # 如果是字符串,记录长度
204
+ if obj.is_a?(String)
205
+ info[:length] = obj.length
206
+ info[:encoding] = obj.encoding.name
207
+ end
208
+
209
+ # 如果是数字,记录值
210
+ if obj.is_a?(Numeric)
211
+ info[:value] = obj
212
+ end
213
+
214
+ info
215
+ rescue => e
216
+ { error: "无法提取对象信息: #{e.message}" }
217
+ end
218
+
219
+ def extract_arguments(binding_context)
220
+ return nil unless binding_context
221
+
222
+ # 尝试获取方法参数(Ruby 2.7+ 支持)
223
+ if binding_context.respond_to?(:local_variables)
224
+ vars = binding_context.local_variables
225
+ args = {}
226
+ vars.each do |var|
227
+ begin
228
+ args[var] = safe_inspect(binding_context.local_variable_get(var))
229
+ rescue
230
+ args[var] = "<unreadable>"
231
+ end
232
+ end
233
+ args
234
+ end
235
+ rescue => e
236
+ { error: e.message }
237
+ end
238
+
239
+ def extract_local_variables(binding_context)
240
+ return nil unless binding_context
241
+
242
+ vars = {}
243
+ binding_context.local_variables.each do |var|
244
+ begin
245
+ value = binding_context.local_variable_get(var)
246
+ vars[var] = {
247
+ class: value.class.name,
248
+ inspect: safe_inspect(value)[0..100] # 限制长度
249
+ }
250
+ rescue => e
251
+ vars[var] = { error: e.message }
252
+ end
253
+ end
254
+ vars
255
+ rescue => e
256
+ { error: e.message }
257
+ end
258
+
259
+ def format_log_entry(**data)
260
+ timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S.%3N")
261
+ indent = data.delete(:indent) || ""
262
+ event = data.delete(:event)
263
+
264
+ @logger ||= []
265
+ record = {"time"=>timestamp, "indent"=>indent.strip, "event"=>event}
266
+
267
+ # 构建格式化的日志行
268
+ lines = ["[#{timestamp}] #{indent}[#{event}]"]
269
+
270
+ data.each do |key, value|
271
+ next if value.nil?
272
+
273
+ formatted_value = case value
274
+ when Hash
275
+ value.map { |k, v| "#{k}=#{truncate(v.to_s, 80)}" }.join(", ")
276
+ when String
277
+ truncate(value, 100)
278
+ else
279
+ truncate(value.to_s, 100)
280
+ end
281
+
282
+ record[key] = formatted_value
283
+ lines << " #{key}: #{formatted_value}"
284
+ end
285
+
286
+ @logger << record
287
+ lines.map{|line|line.encode("UTF-8")}.join("\n")
288
+ end
289
+
290
+ def read_source_line(file, line_num)
291
+ return nil unless File.exist?(file)
292
+
293
+ File.readlines(file)[line_num - 1]&.strip
294
+ rescue => e
295
+ "<无法读取: #{e.message}>"
296
+ end
297
+
298
+ def safe_inspect(obj)
299
+ obj.inspect
300
+ rescue => e
301
+ "<inspect error: #{e.message}>"
302
+ end
303
+
304
+ def truncate(str, max_len)
305
+ str.length > max_len ? str[0...max_len] + "..." : str
306
+ end
307
+
308
+ def internal_file?(path)
309
+ return true if path.nil? || path.empty?
310
+ return true if path.start_with?('<internal:')
311
+ return true if path.include?('/ruby/')
312
+ return true if path.include?('execution_tracer.rb') # 忽略追踪器本身
313
+ false
314
+ end
315
+ end
316
+ end
317
+
318
+ # 自动启动(如果设置了环境变量)
319
+ if ENV['EXECUTION_TRACER_AUTO_START']
320
+ ExecutionTracer.enable!(
321
+ log_path: ENV['EXECUTION_TRACER_LOG'] || 'execution.log',
322
+ trace_lines: ENV['EXECUTION_TRACER_TRACE_LINES'] == 'true'
323
+ )
324
+ end
325
+
326
+ # 注册退出钩子
327
+ at_exit { ExecutionTracer.disable! if ExecutionTracer.enabled }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cc
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.5
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt
@@ -32,7 +32,9 @@ files:
32
32
  - monkey-patch.rb
33
33
  - number.rb
34
34
  - regexp.rb
35
+ - shell-tools.rb
35
36
  - string.rb
37
+ - trace.rb
36
38
  - tree.rb
37
39
  homepage: https://github.com/ChenMeng1365/custom-core
38
40
  licenses: