cinnabar 0.0.3 → 0.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b77622e5e559801f83245411e113493c2735390a17cbd93cd3286a2db1c8b5a
4
- data.tar.gz: 80654c7b5dcaf3debb68aaed1dabbe4f4391eff86269da91bb532aac0b79196a
3
+ metadata.gz: b8b6184f3c8af69dfce30f53af0f8fcad8b34a380dae6bc3e21c32f3bde4753b
4
+ data.tar.gz: aa0f40c2a071eb846d01db60bdf60a9b738d8a3f0fe76e70bf57d48fa670376b
5
5
  SHA512:
6
- metadata.gz: 0e2f058289338aee86c2eca9f5b33cb4d98c55669f98677ca2cc8967e390231cb8b54479acccedafef9166d28f60d9dde8bbfbc60e7db20b846ce372ffda8d70
7
- data.tar.gz: d8ee09d5461bfbb40aa21b4b2ba155bd33d765630034815843d358d11c01ac5b70b089c72242deba02ba5ffcae60f64e9c0f2133cc392da04e922de49bd8eeb9
6
+ metadata.gz: 866a074b21a09531dd22c8f237f25ea9b86f96c2a431d99b010d7b375def9f58947f244ee67d4177c442c82bc16b7b4c578ad75ad5768ac90baae009663d714a
7
+ data.tar.gz: 5f65f8f023ba4281cc6afb755f8ab3f5f73faa627096a81cb2c78734ef14b36f686ee87ddd91912b07c2d8630a79730c2c7a3918be5723a7aa9ed2b87b388d8f
data/.rubocop.yml CHANGED
@@ -26,6 +26,8 @@ Style/Documentation:
26
26
  Enabled: false
27
27
  Style/ClassAndModuleChildren:
28
28
  EnforcedStyle: compact
29
+ Style/RescueModifier:
30
+ Enabled: false
29
31
 
30
32
  # https://docs.rubocop.org/rubocop/cops_metrics.html
31
33
  Metrics/MethodLength:
@@ -42,4 +44,3 @@ Layout/MultilineMethodCallIndentation:
42
44
  Naming/AsciiIdentifiers:
43
45
  # AsciiConstants: false
44
46
  Enabled: false
45
-
data/docs/ChangeLog.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # ChangeLog
2
2
 
3
+ ## v0.0.4 (2026-01-15)
4
+
5
+ > TL;DR: Refactor Path logic, introduce GemPath & Utils, update CI loading, and adjust RuboCop rules
6
+
7
+ - Introduced `Cinnabar::GemPath` and replaced legacy `Cinnabar::Path` gem‑loading logic.
8
+ - Updated `ci.rb` to use `new_gem_path_proc` and JSON cache file.
9
+ - Updated gem_path cache format:
10
+ - DSL:`"#{k1}#{2spaces}#{v1}"` => json: `{"#{k1}": [v1, v2]}`
11
+ - Removed large monolithic gem‑path utilities from `path.rb`.
12
+ - Added `Cinnabar::Utils`
13
+ - Adjusted `.rubocop.yml`: disabled `Style/RescueModifier`.
14
+ - Added **misc/firb**
15
+
3
16
  ## v0.0.2 (2026-01-05)
4
17
 
5
18
  - fix ruby v4.0.0 compatibility
data/docs/Readme-zh.md CHANGED
@@ -24,9 +24,10 @@
24
24
  2. 朱砂有毒。这个项目是为了 **猛、糙、快** (a.k.a. *Dirty and Quick*) 的目的而开发的,可能会产生意料之外的副作用(它并非完全无害)。
25
25
  3. 朱砂是一种硫化汞 (HgS) 的矿物,呈深红色,而 Ruby 也是一种深红色的宝石。给一个 Ruby 项目取名为 “Cinnabar(朱砂)” 非常贴切。
26
26
 
27
- ## API DOC
27
+ ## 文档
28
28
 
29
- ![ClassDiagram](../misc/assets/svg/ClassDiagram.svg)
29
+ [![ClassDiagram](../misc/assets/svg/ClassDiagram.svg)](https://raw.githubusercontent.com/2moe/cinnabar/refs/heads/main/misc/assets/svg/ClassDiagram.svg)
30
30
 
31
31
  - Github Pages: <https://2moe.github.io/cinnabar>
32
-
32
+ - [变更日志](./ChangeLog.md)
33
+ - 详见[英文 Readme](./Readme.md)
data/docs/Readme.md CHANGED
@@ -24,11 +24,12 @@ A:
24
24
  2. Cinnabar is toxic. This project was developed for *Dirty and Quick* purposes and may produce unexpected side effects—in a sense, it is not entirely harmless.
25
25
  3. Cinnabar, a mineral form of mercury sulfide (HgS), is a deep red-colored stone. And ruby is also a deep red stone. Naming a Ruby project "Cinnabar" is particularly fitting.
26
26
 
27
- ## API DOC
27
+ ## Documentation
28
28
 
29
- ![ClassDiagram](../misc/assets/svg/ClassDiagram.svg)
29
+ [![ClassDiagram](../misc/assets/svg/ClassDiagram.svg)](https://raw.githubusercontent.com/2moe/cinnabar/refs/heads/main/misc/assets/svg/ClassDiagram.svg)
30
30
 
31
31
  - Github Pages: <https://2moe.github.io/cinnabar>
32
+ - [ChangeLog](./ChangeLog.md)
32
33
 
33
34
  ## Quick Start
34
35
 
@@ -156,3 +157,25 @@ jobs:
156
157
  .▷(upper)
157
158
  .▷ :puts #=> "FOO"
158
159
  ```
160
+
161
+ ### String To Pathname
162
+
163
+ ```ruby,yaml
164
+ - run: |
165
+ __dir__.to_path # Same as Pathname(__dir__)
166
+ .join('tmp')
167
+ ```
168
+
169
+ ### GemPath
170
+
171
+ #### Faster IRB
172
+
173
+ > **NON CI Environment**
174
+
175
+ 1. run: [misc/firb/install-cinnabar.ps1](../misc/firb/install-cinnabar.ps1)
176
+ 2. Enter `${XDG_CACHE_HOME:-~/.cache}/ruby/firb/bin/`
177
+ 3. run
178
+ - [`./firb0`](../misc/firb/bin/firb0)
179
+ - OR `.\firb0.bat` (Windows)
180
+ - OR [`./firb`](../misc/firb/bin/firb)
181
+ - OR `.\firb.bat` (Windows)
@@ -0,0 +1,435 @@
1
+ # frozen_string_literal: true
2
+
3
+ # NOTE: This file can be run independently.
4
+
5
+ require 'pathname'
6
+
7
+ module Cinnabar
8
+ # Returns a factory `Proc` that builds a {Cinnabar::GemPath} instance.
9
+ #
10
+ # This is useful when you want to defer initialization or inject a constructor-like
11
+ # callable into other components (e.g., configuration objects, DI containers, hooks).
12
+ #
13
+ # ## Behavior
14
+ #
15
+ # - The returned `Proc` accepts a single argument `opts`.
16
+ # - When called, it creates a new {Cinnabar::GemPath} with `opts`.
17
+ #
18
+ # @param opts [Hash] options forwarded to {Cinnabar::GemPath#initialize}
19
+ # @option opts [String] :cache_dir base directory for the JSON cache
20
+ # @option opts [String] :cache_file cache file name or absolute path
21
+ # @option opts [Array<String,Symbol>] :gems gem names to resolve
22
+ # @option opts [Boolean] :install_gem whether to auto-install missing gems
23
+ #
24
+ # @return [Proc] a callable object: `(opts) -> Cinnabar::GemPath`
25
+ #
26
+ # @example Build and use a resolver on demand
27
+ #
28
+ # {
29
+ # gems: %w[rdoc logger irb reline fiddle],
30
+ # install_gem: true
31
+ # }.then(&Cinnabar.new_gem_path_proc)
32
+ # .append_load_path!
33
+ #
34
+ # @see Cinnabar::GemPath
35
+ def self.new_gem_path_proc = ->(opts) { Cinnabar::GemPath.new(opts) }
36
+ end
37
+
38
+ # Low-level helpers for resolving RubyGems load paths.
39
+ #
40
+ # This module provides the "expensive" operations: loading RubyGems, locating gem specs, and
41
+ # optionally installing missing gems.
42
+ #
43
+ # ## Design goals
44
+ #
45
+ # - **Lazy-load RubyGems**: do not `require 'rubygems'` at file load time.
46
+ # - **Return full require paths**: use RubyGems’ resolved paths rather than hand-joining.
47
+ # - **Be usable by higher-level caches**: see {Cinnabar::GemPath}.
48
+ #
49
+ # @note These functions may perform disk I/O and can be slow.
50
+ module Cinnabar::GemPathCore
51
+ module_function
52
+
53
+ # Ensures RubyGems is loaded.
54
+ #
55
+ # @note If Ruby was not started with `--disable=gems` option, this function is unnecessary.
56
+ #
57
+ # @note This function **must** be called only at the point where RubyGems is
58
+ # actually needed (lazy loading).
59
+ #
60
+ # ## Behavior
61
+ #
62
+ # - If RubyGems is already available (`::Gem` and `Gem::Specification`), this is a no-op.
63
+ # - Otherwise, it calls `require 'rubygems'`.
64
+ #
65
+ # @return [void]
66
+ #
67
+ # @note Do not call `require 'rubygems'` outside this method.
68
+ def ensure_rubygems!
69
+ return if defined?(::Gem) && ::Gem.respond_to?(:Specification)
70
+
71
+ Kernel.require 'rubygems'
72
+ end
73
+
74
+ # Returns full load paths for a gem's require paths.
75
+ #
76
+ # This uses `Gem::Specification#full_require_paths`, which returns **absolute**
77
+ # directories that should be added to `$LOAD_PATH` to require files from the gem.
78
+ #
79
+ # @param gem_name [String] gem name (e.g. `"logger"` or `:logger`)
80
+ # @return [Array<String>] absolute require directories (e.g. `["/path/to/gems/foo/lib"]`)
81
+ #
82
+ # @raise [Gem::LoadError] if the gem is not installed / cannot be found
83
+ # @example Get full require paths
84
+ # Cinnabar::GemPathCore.find_lib_paths("logger")
85
+ def find_lib_paths(gem_name)
86
+ ensure_rubygems!
87
+
88
+ Gem::Specification.find_by_name(gem_name).full_require_paths
89
+ end
90
+
91
+ # Returns the gem's load paths; installs the gem when missing.
92
+ #
93
+ # This method is intended for low-level bootstrap scenarios:
94
+ #
95
+ # - Try to resolve require paths via RubyGems.
96
+ # - If it fails, warn to stderr and attempt `Gem.install(gem)`.
97
+ # - Re-resolve paths after successful installation.
98
+ #
99
+ # ## Notes
100
+ #
101
+ # - This method intentionally prints warnings via {Kernel.warn}.
102
+ # - Avoid using "advanced" loggers here, because this method may run before
103
+ # other dependencies are available.
104
+ #
105
+ # @param gem_name [String, Symbol] gem name
106
+ # @return [Array<String>] absolute require directories for the gem
107
+ #
108
+ # @raise [RuntimeError] if installation returns `nil` or an empty spec list
109
+ # @raise [Exception] any error raised by RubyGems or filesystem operations
110
+ #
111
+ # @example Resolve paths, installing automatically if missing
112
+ #
113
+ # logger_lib_paths = Cinnabar::GemPathCore.find_or_install_lib_paths("logger")
114
+ def find_or_install_lib_paths(gem_name)
115
+ ensure_rubygems!
116
+
117
+ gem = gem_name.to_s
118
+ begin
119
+ find_lib_paths(gem)
120
+
121
+ # NOTE: Intentionally rescuing `Exception` here (see original intent).
122
+ rescue Exception => e # rubocop:disable Lint/RescueException
123
+ Kernel.warn "[WARN] #{e}; Try installing #{gem}"
124
+
125
+ # Attempt to install; raise if installation fails.
126
+ specs = Gem.install(gem)
127
+ Kernel.raise "Failed to install #{gem}" if specs.nil? || specs.empty?
128
+
129
+ find_lib_paths(gem)
130
+ end
131
+ end
132
+
133
+ # Builds a mapping from gem name to resolved load paths.
134
+ #
135
+ # @param gems [Array<String>] gem names
136
+ # @param install_gem [Boolean] whether to attempt installing missing gems
137
+ # @return [Hash{(String)=>Array<String>}] mapping: gem_name => full require paths
138
+ #
139
+ # @example Build a mapping without installing
140
+ # Cinnabar::GemPathCore.init_gem_dir_hash(%w[rdoc logger irb reline fiddle], install_gem: false)
141
+ def init_gem_dir_hash(gems, install_gem: true)
142
+ gems.map { |name|
143
+ paths =
144
+ if install_gem
145
+ find_or_install_lib_paths(name)
146
+ else
147
+ find_lib_paths(name)
148
+ end
149
+
150
+ [name, paths]
151
+ }.to_h
152
+ end
153
+ end
154
+
155
+ # Cached resolver for gem load paths.
156
+ #
157
+ # {Cinnabar::GemPathCore} performs expensive operations (loading RubyGems, resolving specs,
158
+ # installing missing gems). This class adds a JSON cache layer to reuse results and
159
+ # avoid repeated slow calls.
160
+ #
161
+ # ## What it caches
162
+ #
163
+ # - A Hash mapping `gem_name => [full_require_paths...]`
164
+ # - Serialized to JSON on disk (see {#cache_file})
165
+ #
166
+ # ## Typical usage
167
+ #
168
+ # 1. Create an instance with a set of gems.
169
+ # 2. Call {#append_load_path!} to add those directories to `$LOAD_PATH`.
170
+ #
171
+ # @note Prefer using the cache whenever possible; RubyGems operations can be slow.
172
+ class Cinnabar::GemPath
173
+ # Alias to the low-level implementation module.
174
+ #
175
+ # @return [Module]
176
+ CoreMod = Cinnabar::GemPathCore
177
+
178
+ require 'json'
179
+
180
+ # The cache file path, gems list, and install behavior.
181
+ #
182
+ # @!attribute [rw] cache_file
183
+ # @return [Pathname] cache file path (`*.json`)
184
+ #
185
+ # @!attribute [rw] gems
186
+ # @return [Array<String>] normalized gem names (unique, non-empty)
187
+ #
188
+ # @!attribute [rw] install_gem
189
+ # @return [Boolean] whether missing gems should be installed automatically
190
+ attr_accessor :cache_file, :gems, :install_gem
191
+
192
+ # The in-memory cache hash.
193
+ #
194
+ # @return [Hash{String=>Array<String>}] gem name => full require paths
195
+ attr_reader :cache_hash
196
+
197
+ # Default options for initialization.
198
+ #
199
+ # @return [Hash]
200
+ # @option cache_dir [String] directory to store cache file (default: `~/.cache/ruby`)
201
+ # @option cache_file [String] file name or absolute path (default: `gem_path.json`)
202
+ # @option gems [Array<String,Symbol>] gem names to manage
203
+ # @option install_gem [Boolean] whether to auto-install missing gems
204
+ DEFAULT_OPTS = {
205
+ cache_dir: File.expand_path('~/.cache/ruby'),
206
+ cache_file: 'gem_path.json',
207
+ gems: [],
208
+ install_gem: true,
209
+ }.freeze
210
+
211
+ # Creates a cache-backed gem path resolver.
212
+ #
213
+ # ## Behavior
214
+ #
215
+ # - Normalizes `gems` to unique, non-empty strings.
216
+ # - If cache file exists, it is decoded and then refreshed:
217
+ # - Missing gems are generated and merged into the cache.
218
+ # - Broken entries (paths not existing) are regenerated.
219
+ # - If no cache exists, it generates paths for all gems and writes the cache.
220
+ #
221
+ # @param opts [Hash] options overriding {DEFAULT_OPTS}
222
+ # @option opts [String] :cache_dir
223
+ # @option opts [String] :cache_file
224
+ # @option opts [Array<String,Symbol>] :gems
225
+ # @option opts [Boolean] :install_gem
226
+ #
227
+ # @return [Cinnabar::GemPath]
228
+ #
229
+ # @example Create and append load paths
230
+ #
231
+ # gems = %w[rdoc logger irb reline fiddle]
232
+ #
233
+ # { gems: }
234
+ # .then { Cinnabar::GemPath.new _1 }
235
+ # .append_load_path!
236
+ def initialize(opts = {})
237
+ options = DEFAULT_OPTS.merge(opts || {})
238
+
239
+ gems, cache_dir, cache_file, @install_gem =
240
+ options.values_at(:gems, :cache_dir, :cache_file, :install_gem)
241
+
242
+ @gems = Array(gems).map(&:to_s).reject(&:empty?).uniq
243
+
244
+ raise 'Empty @gems!' if @gems.empty?
245
+
246
+ @cache_file = init_cache_file(cache_dir, cache_file)
247
+ @cache_hash = {}
248
+
249
+ if @cache_file.exist?
250
+ decode_cache_file
251
+ return
252
+ end
253
+
254
+ merge_generated_paths!(@gems)
255
+ end
256
+
257
+ # Writes the in-memory cache to disk.
258
+ #
259
+ # This serializes {#cache_hash} as JSON and persists it using an atomic write
260
+ # strategy (see {#atomic_write_cache_file}).
261
+ #
262
+ # @return [void]
263
+ def update_cache_file
264
+ @cache_hash
265
+ .then { JSON.dump _1 }
266
+ .then { atomic_write_cache_file _1 }
267
+ end
268
+
269
+ # Appends cached gem paths into `$LOAD_PATH`.
270
+ #
271
+ # This is typically called after initialization. It will add each directory in the
272
+ # cache for the configured gems to Ruby's load path.
273
+ #
274
+ # ## Notes
275
+ #
276
+ # - This is **idempotent**: it avoids inserting duplicates.
277
+ # - It mutates the global `$LOAD_PATH` (`$:`).
278
+ #
279
+ # @return [void]
280
+ def append_load_path!
281
+ @cache_hash
282
+ .filter { |k, _| @gems.include? k.to_s }
283
+ .each_value do |vals|
284
+ Array(vals).each do |dir|
285
+ # $: is $LOAD_PATH
286
+ $:.push(dir) unless $:.include?(dir) # rubocop:disable Style/SpecialGlobalVars
287
+ end
288
+ end
289
+ end
290
+
291
+ protected
292
+
293
+ # Writes cache content to disk atomically.
294
+ #
295
+ # It writes to a temporary file and renames it to the final cache path.
296
+ # This reduces the chance of leaving a partially-written JSON file when the
297
+ # process crashes or is interrupted mid-write.
298
+ #
299
+ # @param content [String] serialized JSON content
300
+ # @return [void]
301
+ #
302
+ # @api private
303
+ def atomic_write_cache_file(content)
304
+ path = @cache_file.to_s
305
+ tmp = "#{path}.tmp.#{$$}" # rubocop:disable Style/SpecialGlobalVars
306
+ begin
307
+ File.write(tmp, content)
308
+ File.rename(tmp, path)
309
+ ensure
310
+ File.unlink(tmp) rescue nil
311
+ end
312
+ end
313
+
314
+ # Resolves the cache file path.
315
+ #
316
+ # - If `file` is absolute, it is used directly.
317
+ # - Otherwise, it is resolved relative to `dir`.
318
+ # - Ensures the directory exists (`mkpath`).
319
+ #
320
+ # @param dir [String, Pathname] base directory
321
+ # @param file [String, Pathname] file name or absolute path
322
+ # @return [Pathname] resolved cache file path
323
+ #
324
+ # @api private
325
+ def init_cache_file(dir, file)
326
+ f = Pathname(file)
327
+
328
+ if f.absolute?
329
+ f
330
+ else
331
+ Pathname(dir).join(f)
332
+ end
333
+ .tap { _1.dirname.mkpath }
334
+ end
335
+
336
+ # Attempts to decode the cache JSON file into a Hash.
337
+ #
338
+ # If decoding fails for any reason, it warns and removes the cache file, then
339
+ # returns an empty hash.
340
+ #
341
+ # @return [Hash] decoded hash (or `{}` if decode fails)
342
+ #
343
+ # @note This method intentionally rescues `Exception` per the original design.
344
+ # @api private
345
+ def try_decode_cache_hash
346
+ @cache_file
347
+ .read
348
+ .then { JSON.parse _1 }
349
+ rescue Exception => e # rubocop:disable Lint/RescueException
350
+ Kernel.warn "[WARN] Failed to decode json file; error: #{e}; unlink #{@cache_file}"
351
+ @cache_file.unlink
352
+ {}
353
+ end
354
+
355
+ # Decodes the cache file and refreshes any missing/broken entries.
356
+ #
357
+ # - Loads {#cache_hash} from disk.
358
+ # - Generates entries for missing gems in {#gems}.
359
+ # - Regenerates entries whose paths no longer exist on disk.
360
+ #
361
+ # @return [void]
362
+ #
363
+ # @api private
364
+ def decode_cache_file
365
+ @cache_hash = try_decode_cache_hash
366
+ refresh_missing_gems!
367
+ refresh_broken_gems!
368
+ end
369
+
370
+ # ===========
371
+ private
372
+
373
+ # Computes gems that are required but absent from the cache.
374
+ #
375
+ # @return [Array<String>]
376
+ def missing_gems
377
+ @gems - @cache_hash.keys
378
+ end
379
+
380
+ # Generates and merges cache entries for missing gems.
381
+ #
382
+ # @return [void]
383
+ def refresh_missing_gems!
384
+ gems = missing_gems
385
+ return if gems.empty?
386
+
387
+ merge_generated_paths!(gems)
388
+ end
389
+
390
+ # ===========
391
+
392
+ # Computes gems whose cached paths are missing on disk.
393
+ #
394
+ # @return [Array<String>]
395
+ def broken_gems
396
+ @cache_hash
397
+ .reject { |_, v| all_paths_exist?(v) }
398
+ .keys
399
+ end
400
+
401
+ # Checks whether all cached paths exist.
402
+ #
403
+ # @param values [Array<String>, Object] cached paths (normally an array)
404
+ # @return [Boolean] true if all entries exist on disk
405
+ def all_paths_exist?(values)
406
+ Array(values).all? { File.exist? _1 }
407
+ end
408
+
409
+ # Regenerates and merges cache entries for broken gems.
410
+ #
411
+ # @return [void]
412
+ def refresh_broken_gems!
413
+ gems = broken_gems
414
+ return if gems.empty?
415
+
416
+ Kernel.warn "[INFO] update gem path, key: #{gems}"
417
+ merge_generated_paths!(gems)
418
+ end
419
+
420
+ # ===========
421
+
422
+ # Generates demonstrates gem paths via {CoreMod} and persists the updated cache.
423
+ #
424
+ # @param gems [Array<String>] gem names to generate
425
+ # @return [void]
426
+ #
427
+ # @raise [Exception] any error raised by RubyGems resolution/installation
428
+ def merge_generated_paths!(gems)
429
+ h2 = CoreMod.init_gem_dir_hash(gems, install_gem: @install_gem)
430
+ return if h2.empty?
431
+
432
+ @cache_hash.merge! h2
433
+ update_cache_file
434
+ end
435
+ end
data/lib/cinnabar/path.rb CHANGED
@@ -1,24 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # NOTE: This file can be run independently;
4
- # Although **00_pre.rb** has already imported the relevant libraries, they still need to be imported here.
5
3
  require 'pathname'
6
4
 
7
5
  module Cinnabar
8
- # Build a Proc that converts a directory-like value into a {Kernel.Pathname}.
6
+ # Build a Proc that converts a string value into a {Kernel.Pathname}.
9
7
  #
10
8
  # This is handy when you want to pass a converter into higher-order APIs
11
9
  # (e.g., map/filter pipelines).
12
10
  #
13
- # @param dir [String, #to_s] a directory path (or any object convertible to String)
14
- # @return [Proc] a lambda that maps `dir` to `Pathname(dir)`
11
+ # @param s [String, Pathname] a path (e.g., file/dir)
12
+ # @return [Proc] a lambda that maps `s` to `Pathname(s)`
15
13
  #
16
- # @example Convert a list of directories to Pathname objects
14
+ # @example convert a list of directories to Pathname objects
17
15
  #
18
16
  # conv = Cinnabar.to_path_proc
19
17
  # %w[/tmp /var].map(&conv)
20
18
  # #=> [#<Pathname:/tmp>, #<Pathname:/var>]
21
- def self.to_path_proc = ->(dir) { Pathname(dir) }
19
+ def self.to_path_proc = ->(s) { Pathname(s) }
22
20
  end
23
21
 
24
22
  # Adds `String#to_path` as a convenience helper to convert strings into {Kernel.Pathname}.
@@ -82,172 +80,3 @@ module Cinnabar::StrToPath
82
80
  end
83
81
  end
84
82
  end
85
-
86
- module Cinnabar::Path
87
- module_function
88
-
89
- # Appends a directory to Ruby's load path (`$LOAD_PATH` / `$:`)
90
- # if it is not already included.
91
- #
92
- # @param dir [String] directory path to add into `$LOAD_PATH`
93
- # @return [void]
94
- # @example
95
- #
96
- # Cinnabar::Path.append_load_path '/opt/ruby/4.0.0/lib/ruby/4.0.0'
97
- def append_load_path(dir)
98
- # $: is an alias of $LOAD_PATH
99
- $: << dir unless $:.include?(dir)
100
- end
101
-
102
- # Finds the gem's "lib directory" (via `gem which -V`) and append it to $LOAD_PATH.
103
- #
104
- # This method maintains a small cache file to avoid running `gem which` repeatedly.
105
- # Cache format (per line):
106
- # <gem_name><two spaces><lib_dir>
107
- #
108
- # If the gem cannot be located, it will try to install it and retry up to `max_retries`.
109
- #
110
- # @param gem_name [String, Symbol] gem name to locate (default: 'logger')
111
- # @param cache_file [String] cache file path (default: 'tmp/load_path.txt')
112
- # @param max_retries [Integer] maximum retries for gem install + re-check (default: 2)
113
- # @return [void]
114
- def find_and_append_load_path(gem_name = 'logger', cache_file: 'tmp/load_path.txt', max_retries: 2)
115
- pkg = gem_name.to_s
116
- cache_data = decode_cache_file(cache_file)
117
-
118
- # If cached, append immediately and return.
119
- case val = cache_data&.[](pkg)
120
- when nil then ()
121
- else return append_load_path(val)
122
- end
123
-
124
- # Locate the gem's lib directory, installing the gem if necessary.
125
- lib_dir = gem_dir_with_retry(pkg, max_retries)
126
-
127
- # Update cache and write it back to disk.
128
- cache_data[pkg] = lib_dir
129
- encoded = encode_cache_hash(cache_data)
130
-
131
- # Ensure parent directory exists, then write the cache file.
132
- Kernel.Pathname(cache_file)
133
- .tap { _1.dirname.mkpath }
134
- .write(encoded)
135
-
136
- # Finally, append the located directory to $LOAD_PATH.
137
- append_load_path(lib_dir)
138
- end
139
-
140
- # Decodes cache file into a Hash.
141
- #
142
- # It ignores:
143
- #
144
- # - leading spaces (lstrip)
145
- # - empty lines
146
- # - comment lines that start with '#'
147
- #
148
- # Each line is split by "two spaces" into:
149
- # `key value`
150
- #
151
- # @param file [String] cache file path
152
- # @return [Hash{String => String}] mapping from gem name to lib dir
153
- def decode_cache_file(file)
154
- # If cache file does not exist, treat as empty cache.
155
- return {} unless File.exist?(file)
156
-
157
- File.foreach(file)
158
- .lazy
159
- .map(&:lstrip) # allow indentation; normalize leading spaces
160
- .map(&:chomp) # remove trailing newline
161
- .reject(&:empty?) # drop blank lines
162
- .reject { _1.start_with? '#' } # drop comments
163
- .map { |line| line.split(' ', 2) } # split into [key, value] by two spaces
164
- .to_h
165
- end
166
-
167
- # Encodes a Hash into the cache file format.
168
- #
169
- # @param data [Hash] mapping from gem name to lib dir
170
- # @return [String] encoded cache content
171
- # @raise [ArgumentError] if `data` is not a Hash
172
- #
173
- # @example
174
- #
175
- # CiPath = Cinnabar::Path
176
- #
177
- # gem_home = "#{Dir.home}/.local/share/gem"
178
- # data = {
179
- # "logger" => "#{gem_home}/gems/logger-1.7.0/lib",
180
- # "irb" => "#{gem_home}/gems/irb-1.16.0/lib",
181
- # "reline" => "#{gem_home}/gems/reline-0.6.3/lib",
182
- # }
183
- # str = CiPath.encode_cache_hash(data)
184
- def encode_cache_hash(data)
185
- Kernel.raise ArgumentError, 'data must be a hash' unless data.is_a? ::Hash
186
-
187
- # Use "two spaces" as a stable delimiter (same as decode).
188
- data.map { |k, v| "#{k} #{v}" }.join("\n")
189
- end
190
-
191
- # Resolves gem's lib directory by invoking:
192
- # `gem which -V <pkg>`
193
- #
194
- # `gem which -V` prints the resolved file path; we strip it and take its dirname.
195
- #
196
- # @param pkg [String] gem name
197
- # @return [String] directory containing the resolved file
198
- # @raise [RuntimeError] if gem which returns empty
199
- #
200
- # @note Please do not use `Gem::Specification` in this method,
201
- # as this function and the script must remain compatible with `--disable=gems`.
202
- def gem_dir(pkg)
203
- path = IO.popen(%w[gem which -V] << pkg.to_s, &:read).to_s.strip
204
- Kernel.raise "gem which returned empty for #{pkg}" if path.empty?
205
-
206
- File.dirname(path)
207
- end
208
- private_class_method :gem_dir
209
-
210
- # Locates the lib directory for the given gem_name; if it fails, retries by installing the gem.
211
- #
212
- # Behavior:
213
- #
214
- # - If `gem_dir` raises, print a warning and run `gem install <pkg>`.
215
- # - Retry up to `max_retries`.
216
- #
217
- # @param gem_name [String, Symbol] gem name
218
- # @param max_retries [Integer] max retry count (default: 2)
219
- # @return [String] resolved lib directory
220
- # @raise [RuntimeError] when install fails or retries exceed max
221
- #
222
- #
223
- # @example
224
- #
225
- # CiPath = Cinnabar::Path
226
- #
227
- # dir_str = CiPath.gem_dir_with_retry("logger")
228
- def gem_dir_with_retry(gem_name, max_retries = 2)
229
- pkg = gem_name.to_s
230
- attempts = 0
231
-
232
- begin
233
- logger_dir = gem_dir(pkg)
234
- rescue StandardError => e
235
- # Inform user about the failure and the planned automatic install attempt.
236
- #
237
- # Do not use `Sinlog.warn` or any "advanced" logger here!
238
- # As this function is intended for lower-level APIs.
239
- Kernel.warn "[WARN] #{e}; Try installing #{pkg}"
240
-
241
- # Attempt to install; raise if installation fails.
242
- Kernel.system "gem install #{pkg}" or Kernel.raise 'Failed to install'
243
-
244
- attempts += 1
245
- Kernel.raise 'Already retried 3 times' if attempts > max_retries
246
-
247
- retry
248
- end
249
-
250
- logger_dir
251
- end
252
- # private_class_method :gem_dir_with_retry
253
- end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cinnabar; end
4
+
5
+ module Cinnabar::Utils
6
+ module_function
7
+
8
+ # @note require 'rubygems'
9
+ def gems! = Kernel.require 'rubygems'
10
+ end
11
+
12
+ module Cinnabar::Utils::OS
13
+ module_function
14
+
15
+ # Returns true if running on Windows (including MSYS/MinGW/Cygwin).
16
+ #
17
+ # @param host_os [String]
18
+ # @return [Boolean]
19
+ #
20
+ # @example
21
+ # Cinnabar::Utils::OS.windows? #=> true/false
22
+ def windows?(host_os = RUBY_PLATFORM)
23
+ host_os.match?(/bccwin|cygwin|djgpp|mingw|mswin|wince/i)
24
+ end
25
+
26
+ # Returns true if running on macOS (Darwin).
27
+ #
28
+ # Ruby convention prefers `macos?` over `macOS?`, but we provide both.
29
+ #
30
+ # @param host_os [String]
31
+ # @return [Boolean]
32
+ #
33
+ # @example
34
+ # Cinnabar::Utils::OS.macos? #=> true/false
35
+ def macOS?(host_os = RUBY_PLATFORM) # rubocop:disable Naming/MethodName
36
+ host_os.match?(/darwin/i)
37
+ end
38
+
39
+ # Returns true if running on Linux.
40
+ #
41
+ # @param host_os [String]
42
+ # @return [Boolean]
43
+ def linux?(host_os = RUBY_PLATFORM)
44
+ host_os.match?(/linux/i)
45
+ end
46
+
47
+ # Returns true if running under WSL 2
48
+ #
49
+ # @param proc_version [String]
50
+ # @return [Boolean]
51
+ # @see wsl_1?
52
+ # @see wsl?
53
+ #
54
+ # @example
55
+ # Cinnabar::Utils::OS.wsl_2? #=> true/false
56
+ def wsl_2?(proc_version = nil)
57
+ proc_version ||= File.read('/proc/version')
58
+
59
+ # proc_version.match?(/(?=.*microsoft)(?=.*-WSL)/)
60
+ proc_version.include?('microsoft') && proc_version.include?('-WSL')
61
+ rescue StandardError
62
+ false
63
+ end
64
+
65
+ # Returns true if running under WSL (Windows Subsystem for Linux).
66
+ def wsl?(proc_version = nil)
67
+ proc_version ||= File.read('/proc/version')
68
+ proc_version.match?(/(M|m)icrosoft/)
69
+ rescue StandardError
70
+ false
71
+ end
72
+
73
+ # Returns true if running under WSL 1
74
+ def wsl_1?(proc_version = nil)
75
+ # wsl 1: Linux version 4.4.0-26100-Microsoft (Microsoft@Microsoft.com) (gcc version 5.4.0 (GCC) ) #7309-Microsoft Fri Jan 01 08:00:00 PST 2016 # rubocop:disable Layout/LineLength
76
+ proc_version ||= File.read('/proc/version')
77
+ proc_version.include?('Microsoft')
78
+ rescue StandardError
79
+ false
80
+ end
81
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cinnabar
4
- VERSION = '0.0.3'
4
+ VERSION = '0.0.4'
5
5
  end
data/lib/cinnabar.rb CHANGED
@@ -5,7 +5,9 @@
5
5
 
6
6
  require_relative 'cinnabar/00_pre'
7
7
  require_relative 'cinnabar/cmd_runner'
8
+ require_relative 'cinnabar/gem_path' unless defined? Cinnabar::GemPathCore
8
9
  require_relative 'cinnabar/net'
9
10
  require_relative 'cinnabar/path'
10
11
  require_relative 'cinnabar/pipe'
12
+ require_relative 'cinnabar/utils' unless defined? Cinnabar::Utils
11
13
  require_relative 'cinnabar/version'
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env -S ruby --disable=gems
2
+ # ====================
3
+ # Depends: ruby (>= 3.1), bat | batcat, eza
4
+ #
5
+ # rubocop:disable Style/GlobalVars
6
+ # rubocop:disable Style/MixinUsage
7
+ # rubocop:disable Style/MultilineBlockChain
8
+ # frozen_string_literal: true
9
+
10
+ # ====================
11
+ # NOTE
12
+ # The first launch may be slow because it generates the cache JSON.
13
+ # Subsequent launches reuse the cache as much as possible for the fastest startup.
14
+ #
15
+ # Usage:
16
+ # `firb` => Enter irb
17
+ # in IRB:
18
+ # l #=> `eza --icons`
19
+ # ll #=> `eza --icons -lh`
20
+ # la #=> `eza --icons -lah`
21
+ # cat 'file' #=> `bat -Pp file`
22
+ # which :ruby #=> `which ruby`
23
+ # cdir '~/.cache' #=> `cd ~/.cache; pwd; ls`
24
+ # pwd #=> Dir.pwd
25
+ # ====================
26
+
27
+ Kernel.warn " ruby:\t#{RUBY_VERSION}" if ARGV.empty?
28
+
29
+ $ruby_cache_dir = -> {
30
+ require 'pathname'
31
+
32
+ env_dir = ENV['xdg_cache_home'.upcase]
33
+ case env_dir
34
+ when nil, ''
35
+ Pathname(Dir.home).join('.cache/ruby')
36
+ else
37
+ Pathname(env_dir).join('ruby')
38
+ end
39
+ }.call
40
+
41
+ -> {
42
+ dir = $ruby_cache_dir.join('lib/cinnabar')
43
+
44
+ %w[gem_path utils]
45
+ .each { require dir.join(_1) }
46
+ }.call
47
+
48
+ def new_cinnabar_gem_path_class(gems)
49
+ {
50
+ cache_dir: $ruby_cache_dir,
51
+ gems:,
52
+ }
53
+ .then(&Cinnabar.new_gem_path_proc)
54
+ end
55
+
56
+ def require_gems = ->(gems) {
57
+ new_cinnabar_gem_path_class(gems)
58
+ .append_load_path!
59
+
60
+ gems.each { require _1 }
61
+ }
62
+
63
+ def start_irb
64
+ %w[
65
+ irb rdoc reline
66
+ ].tap { _1 << 'fiddle' if Cinnabar::Utils::OS.windows? }
67
+ .then(&require_gems)
68
+
69
+ two_spaces = ' '
70
+ Kernel.warn <<~GEM_VER
71
+ #{two_spaces}irb:\t#{IRB::VERSION}
72
+ #{two_spaces}rdoc:\t#{RDoc::VERSION}
73
+ #{Dir.pwd}
74
+ GEM_VER
75
+ IRB.start
76
+ end
77
+
78
+ def run_ruby_cli
79
+ require 'rbconfig'
80
+ ENV['RUBYOPT'] = '--disable=gems'
81
+ exec RbConfig.ruby, *ARGV
82
+ end
83
+
84
+ -> {
85
+ %w[
86
+ logger
87
+ sinlog
88
+ argvise
89
+ cinnabar
90
+ ]
91
+ .then(&require_gems)
92
+
93
+ two_spaces = ' '
94
+ Kernel.warn <<~GEM_VER
95
+ #{two_spaces}argvise: #{Argvise::VERSION}
96
+ #{two_spaces}cinnabar: #{Cinnabar::VERSION}
97
+ #{two_spaces}sinlog: #{Sinlog::VERSION}
98
+ GEM_VER
99
+ }.call
100
+
101
+ include Cinnabar::FnPipe::Mixin
102
+ include Cinnabar::Downloader::StrMixin
103
+ include Cinnabar::Command::ArrMixin
104
+ include Cinnabar::Command::TaskArrMixin
105
+ include Cinnabar::StrToPath::Mixin
106
+ include Sinlog::Mixin
107
+ include Argvise::HashMixin
108
+
109
+ # load `which` & `sh-utils`
110
+ -> {
111
+ gem = 'cinnabar'
112
+
113
+ new_cinnabar_gem_path_class([gem])
114
+ .cache_hash[gem]
115
+ .last
116
+ .then { File.expand_path('../misc/firb/lib', _1).to_path }
117
+ .tap do |dir|
118
+ %w[which sh_utils]
119
+ .each { require dir.join(_1) }
120
+ end
121
+ }.call
122
+ # require_relative '../lib/which'
123
+ # require_relative '../lib/sh_utils'
124
+
125
+ # ====================
126
+ run_ruby_cli unless ARGV.empty?
127
+
128
+ start_irb
@@ -0,0 +1 @@
1
+ @ruby.exe --disable=gems "%~dpn0" %*
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env -S ruby --disable=gems
2
+ # ====================
3
+ # Depends: ruby (>= 3.1)
4
+ #
5
+ # rubocop:disable Style/GlobalVars
6
+ # frozen_string_literal: true
7
+
8
+ # ====================
9
+ # NOTE: call `Cinnabar::Utils.gems!` or `require 'rubygems'` to re-enable `::Gem` module.
10
+ # For faster loading, consider using `gems.then(&require_gems)` instead of `require 'rubygems'`;
11
+ # e.g., `%w[irb rdoc reline].then(&require_gems)`
12
+ #
13
+ # The first run automatically generates a cache JSON, so it may take a little longer.
14
+ # On the second run and beyond, it reuses the cache whenever possible to maximize startup speed.
15
+ #
16
+ # Usage:
17
+ # - `firb0` => Start IRB
18
+ # - `firb0 -h` => Display help info
19
+ # - `firb0 -e "puts 2"` => Equivalent to `ruby -e "puts 2"`, but with `ENV['RUBYOPT'] = '--disable=gems'`
20
+
21
+ Kernel.warn " ruby:\t#{RUBY_VERSION}" if ARGV.empty?
22
+
23
+ $ruby_cache_dir = -> {
24
+ require 'pathname'
25
+
26
+ env_dir = ENV['xdg_cache_home'.upcase]
27
+ case env_dir
28
+ when nil, ''
29
+ Pathname(Dir.home).join('.cache/ruby')
30
+ else
31
+ Pathname(env_dir).join('ruby')
32
+ end
33
+ }.call
34
+
35
+ -> {
36
+ dir = $ruby_cache_dir.join('lib/cinnabar')
37
+
38
+ %w[gem_path utils]
39
+ .each { require dir.join(_1) }
40
+ }.call
41
+
42
+ def new_cinnabar_gem_path_class(gems)
43
+ {
44
+ cache_dir: $ruby_cache_dir,
45
+ gems:,
46
+ }
47
+ .then(&Cinnabar.new_gem_path_proc)
48
+ end
49
+
50
+ def require_gems = ->(gems) {
51
+ new_cinnabar_gem_path_class(gems)
52
+ .append_load_path!
53
+
54
+ gems.each { require _1 }
55
+ }
56
+
57
+ def start_irb
58
+ %w[
59
+ irb rdoc reline
60
+ ].tap { _1 << 'fiddle' if Cinnabar::Utils::OS.windows? }
61
+ .then(&require_gems)
62
+
63
+ two_spaces = ' '
64
+
65
+ Kernel.warn <<~GEM_VER
66
+ #{two_spaces}irb:\t#{IRB::VERSION}
67
+ #{two_spaces}rdoc:\t#{RDoc::VERSION}
68
+ #{Dir.pwd}
69
+ GEM_VER
70
+
71
+ IRB.start
72
+ end
73
+
74
+ unless ARGV.empty?
75
+ require 'rbconfig'
76
+ ENV['RUBYOPT'] = '--disable=gems'
77
+ exec RbConfig.ruby, *ARGV
78
+ end
79
+
80
+ start_irb
@@ -0,0 +1 @@
1
+ @ruby.exe --disable=gems "%~dpn0" %*
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env pwsh
2
+ # Usage:
3
+ # UNIX:
4
+ # sh install-cinnabar.ps1
5
+ # Windows: Copy everything in this file and paste it into PowerShell to run.
6
+ #
7
+ # Description: install cinnabar and copy to cache dir
8
+ # Note: Although the shebang is `pwsh`, you can run it with POSIX sh.
9
+ # Depens: ruby (>= 3.1)
10
+
11
+ gem install cinnabar
12
+
13
+ ruby -r pathname -r cinnabar -r fileutils -e "
14
+ gem = 'cinnabar'
15
+ env_dir = ENV['xdg_cache_home'.upcase]
16
+ cache_dir =
17
+ case env_dir
18
+ when nil, ''
19
+ Pathname(Dir.home).join('.cache')
20
+ else Pathname(env_dir)
21
+ end
22
+ .join('ruby')
23
+ .tap(&:mkpath)
24
+
25
+ copy_to_cache_dir = ->src { FileUtils.cp_r src, cache_dir, verbose: true }
26
+
27
+ lib_dir = Cinnabar::GemPathCore
28
+ .find_lib_paths(gem)
29
+ .first
30
+
31
+ firb = File.expand_path('../misc/firb', lib_dir)
32
+
33
+ [lib_dir, firb]
34
+ .each(&copy_to_cache_dir)
35
+ "
@@ -0,0 +1,65 @@
1
+ # Depends: bat | batcat, eza
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'which'
6
+
7
+ BAT_CAT_CMD = -> {
8
+ %w[bat batcat]
9
+ .each { return [_1, '-Pp'] if which(_1) }
10
+ ['cat']
11
+ }.call
12
+
13
+ EXA_LS_CMD = -> {
14
+ %w[eza exa]
15
+ .each { return [_1, '--icons=auto'] if which(_1) }
16
+ ['ls', '--color=auto']
17
+ }.call
18
+
19
+ # Similar to `cd "/path/to/dir"; pwd; ls`
20
+ def cdir(path = Dir.home)
21
+ path = path_sym_to_str(path)
22
+ File
23
+ .expand_path(path)
24
+ .then { Dir.chdir _1 }
25
+ puts Dir.pwd
26
+ system(*EXA_LS_CMD)
27
+ end
28
+
29
+ def path_sym_to_str(path)
30
+ path.is_a?(Symbol) ? path.to_s : path
31
+ end
32
+
33
+ def run_eza_ls(path = '.', *rest)
34
+ cmd = EXA_LS_CMD
35
+ cmd.concat(rest) unless rest.empty?
36
+ path = path_sym_to_str(path)
37
+ cmd << File.expand_path(path)
38
+ puts "\e[9m #{cmd} \e[0m"
39
+ system(*cmd)
40
+ end
41
+ alias l run_eza_ls
42
+
43
+ def ll(path = '.', *rest)
44
+ argv = %w[-l -h]
45
+ argv.concat(rest) unless rest.empty?
46
+ run_eza_ls(path, *argv)
47
+ end
48
+
49
+ def la(path = '.', *rest)
50
+ argv = %w[-a -l -h]
51
+ argv.concat(rest) unless rest.empty?
52
+ run_eza_ls(path, *argv)
53
+ end
54
+
55
+ def cat(path, *rest)
56
+ cmd = BAT_CAT_CMD
57
+ path = path_sym_to_str(path)
58
+ cmd << File.expand_path(path)
59
+ cmd.concat(rest) unless rest.empty?
60
+ system(*cmd)
61
+ end
62
+
63
+ def pwd
64
+ Dir.pwd
65
+ end
@@ -0,0 +1,60 @@
1
+ # rubocop:disable Layout/LineLength
2
+ # rubocop:disable Style/SpecialGlobalVars
3
+ #
4
+ # frozen_string_literal: true
5
+
6
+ def capture_stdout(command)
7
+ out = IO.popen(command, 'r', &:read)
8
+ return nil unless $?.success?
9
+
10
+ out
11
+ rescue Errno::ENOENT
12
+ nil
13
+ end
14
+
15
+ # @example Linux
16
+ #
17
+ # which 'ls'
18
+ # # => ["/usr/bin/ls"]
19
+ # which :ruby
20
+ # # => ["/opt/ruby/latest/bin/ruby"]
21
+ #
22
+ # @example Windows
23
+ #
24
+ # which :cl
25
+ # # => ["C:\\Program Files\\Microsoft Visual Studio\\18\\Community\\VC\\Tools\\MSVC\\14.50.35717\\bin\\Hostx64\\x64\\cl.exe"]
26
+ def which(cmd) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
27
+ cmd = cmd.to_s if cmd.is_a? Symbol
28
+ return warn 'Try: show_source your_cmd' unless cmd.is_a?(String)
29
+
30
+ probes =
31
+ if Cinnabar::Utils::OS.windows?
32
+ %w[where which command]
33
+ else
34
+ %w[which command where]
35
+ end
36
+
37
+ strip_cmds = ->(out) {
38
+ out
39
+ .to_s
40
+ .lines
41
+ .map(&:strip)
42
+ .reject(&:empty?)
43
+ }
44
+
45
+ probes.each do |probe|
46
+ out =
47
+ case probe
48
+ when 'command'
49
+ capture_stdout %w[sh -c].push("command -v #{cmd}")
50
+ else
51
+ capture_stdout [probe, cmd]
52
+ end
53
+ next if out.nil? || out.empty?
54
+
55
+ cmds = strip_cmds[out]
56
+ return cmds unless cmds.empty?
57
+ end
58
+
59
+ nil
60
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cinnabar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - 2moe
@@ -37,10 +37,19 @@ files:
37
37
  - lib/cinnabar.rb
38
38
  - lib/cinnabar/00_pre.rb
39
39
  - lib/cinnabar/cmd_runner.rb
40
+ - lib/cinnabar/gem_path.rb
40
41
  - lib/cinnabar/net.rb
41
42
  - lib/cinnabar/path.rb
42
43
  - lib/cinnabar/pipe.rb
44
+ - lib/cinnabar/utils.rb
43
45
  - lib/cinnabar/version.rb
46
+ - misc/firb/bin/firb
47
+ - misc/firb/bin/firb.bat
48
+ - misc/firb/bin/firb0
49
+ - misc/firb/bin/firb0.bat
50
+ - misc/firb/install-cinnabar.ps1
51
+ - misc/firb/lib/sh_utils.rb
52
+ - misc/firb/lib/which.rb
44
53
  homepage: https://github.com/2moe/cinnabar
45
54
  licenses:
46
55
  - Apache-2.0