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 +4 -4
- data/.rubocop.yml +2 -1
- data/docs/ChangeLog.md +13 -0
- data/docs/Readme-zh.md +4 -3
- data/docs/Readme.md +25 -2
- data/lib/cinnabar/gem_path.rb +435 -0
- data/lib/cinnabar/path.rb +5 -176
- data/lib/cinnabar/utils.rb +81 -0
- data/lib/cinnabar/version.rb +1 -1
- data/lib/cinnabar.rb +2 -0
- data/misc/firb/bin/firb +128 -0
- data/misc/firb/bin/firb.bat +1 -0
- data/misc/firb/bin/firb0 +80 -0
- data/misc/firb/bin/firb0.bat +1 -0
- data/misc/firb/install-cinnabar.ps1 +35 -0
- data/misc/firb/lib/sh_utils.rb +65 -0
- data/misc/firb/lib/which.rb +60 -0
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b8b6184f3c8af69dfce30f53af0f8fcad8b34a380dae6bc3e21c32f3bde4753b
|
|
4
|
+
data.tar.gz: aa0f40c2a071eb846d01db60bdf60a9b738d8a3f0fe76e70bf57d48fa670376b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
##
|
|
27
|
+
## 文档
|
|
28
28
|
|
|
29
|
-

|
|
29
|
+
[](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
|
-
##
|
|
27
|
+
## Documentation
|
|
28
28
|
|
|
29
|
-

|
|
29
|
+
[](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
|
|
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
|
|
14
|
-
# @return [Proc] a lambda that maps `
|
|
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
|
|
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 = ->(
|
|
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
|
data/lib/cinnabar/version.rb
CHANGED
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'
|
data/misc/firb/bin/firb
ADDED
|
@@ -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" %*
|
data/misc/firb/bin/firb0
ADDED
|
@@ -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(©_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.
|
|
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
|