ryac 0.2.0
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +145 -0
- data/Rakefile +45 -0
- data/Steepfile +9 -0
- data/bin/console +11 -0
- data/bin/ryac +134 -0
- data/bin/setup +8 -0
- data/lib/ryac/analysis/constant/collection.rb +332 -0
- data/lib/ryac/analysis/constant/rename_mapping.rb +346 -0
- data/lib/ryac/analysis/cvar/collection.rb +37 -0
- data/lib/ryac/analysis/gvar/collection.rb +36 -0
- data/lib/ryac/analysis/gvar/rename_mapping.rb +82 -0
- data/lib/ryac/analysis/ivar/collection.rb +281 -0
- data/lib/ryac/analysis/keyword/collection.rb +213 -0
- data/lib/ryac/analysis/keyword/rename_mapping.rb +167 -0
- data/lib/ryac/analysis/local_scopes.rb +582 -0
- data/lib/ryac/analysis/method/collection.rb +298 -0
- data/lib/ryac/analysis/method/rename_mapping.rb +337 -0
- data/lib/ryac/analysis/method_aliases.rb +83 -0
- data/lib/ryac/analysis/method_aliasing.rb +65 -0
- data/lib/ryac/analysis/nesting.rb +123 -0
- data/lib/ryac/analysis/scope_management.rb +25 -0
- data/lib/ryac/analysis/site_bucket_mapping.rb +127 -0
- data/lib/ryac/analysis/type_oracle.rb +386 -0
- data/lib/ryac/ast_utils.rb +321 -0
- data/lib/ryac/errors.rb +32 -0
- data/lib/ryac/gem_resolver.rb +55 -0
- data/lib/ryac/minifier.rb +169 -0
- data/lib/ryac/name_generator.rb +68 -0
- data/lib/ryac/pipeline/analyzer.rb +264 -0
- data/lib/ryac/pipeline/attr_decl_shorten.rb +61 -0
- data/lib/ryac/pipeline/boolean_shorten.rb +26 -0
- data/lib/ryac/pipeline/char_shorten.rb +20 -0
- data/lib/ryac/pipeline/compactor.rb +1202 -0
- data/lib/ryac/pipeline/concatenator.rb +248 -0
- data/lib/ryac/pipeline/constant_aliaser.rb +240 -0
- data/lib/ryac/pipeline/constant_fold.rb +93 -0
- data/lib/ryac/pipeline/control_flow_simplify.rb +352 -0
- data/lib/ryac/pipeline/data_types.rb +179 -0
- data/lib/ryac/pipeline/endless_method.rb +60 -0
- data/lib/ryac/pipeline/errors.rb +94 -0
- data/lib/ryac/pipeline/file_collector.rb +360 -0
- data/lib/ryac/pipeline/method_renamer.rb +151 -0
- data/lib/ryac/pipeline/paren_optimizer.rb +161 -0
- data/lib/ryac/pipeline/rename_patcher.rb +274 -0
- data/lib/ryac/pipeline/source_patcher.rb +54 -0
- data/lib/ryac/pipeline/spelling_shorten.rb +60 -0
- data/lib/ryac/pipeline/stage.rb +72 -0
- data/lib/ryac/pipeline/stage_runner.rb +84 -0
- data/lib/ryac/pipeline/variable_renamer.rb +125 -0
- data/lib/ryac/union_find.rb +42 -0
- data/lib/ryac/version.rb +5 -0
- data/lib/ryac.rb +29 -0
- data/rbs_collection.lock.yaml +224 -0
- data/rbs_collection.yaml +25 -0
- data/sig/ryac/analysis/constant/collection.rbs +30 -0
- data/sig/ryac/analysis/constant/rename_mapping.rbs +60 -0
- data/sig/ryac/analysis/cvar/collection.rbs +11 -0
- data/sig/ryac/analysis/gvar/collection.rbs +9 -0
- data/sig/ryac/analysis/gvar/rename_mapping.rbs +23 -0
- data/sig/ryac/analysis/ivar/collection.rbs +26 -0
- data/sig/ryac/analysis/keyword/collection.rbs +27 -0
- data/sig/ryac/analysis/keyword/rename_mapping.rbs +31 -0
- data/sig/ryac/analysis/local_scopes.rbs +77 -0
- data/sig/ryac/analysis/method/collection.rbs +25 -0
- data/sig/ryac/analysis/method/rename_mapping.rbs +82 -0
- data/sig/ryac/analysis/method_aliases.rbs +10 -0
- data/sig/ryac/analysis/method_aliasing.rbs +9 -0
- data/sig/ryac/analysis/nesting.rbs +9 -0
- data/sig/ryac/analysis/scope_management.rbs +5 -0
- data/sig/ryac/analysis/site_bucket_mapping.rbs +26 -0
- data/sig/ryac/analysis/type_oracle.rbs +61 -0
- data/sig/ryac/ast_utils.rbs +34 -0
- data/sig/ryac/errors.rbs +21 -0
- data/sig/ryac/gem_resolver.rbs +28 -0
- data/sig/ryac/minifier.rbs +33 -0
- data/sig/ryac/name_generator.rbs +14 -0
- data/sig/ryac/pipeline/analyzer.rbs +34 -0
- data/sig/ryac/pipeline/attr_decl_shorten.rbs +17 -0
- data/sig/ryac/pipeline/boolean_shorten.rbs +11 -0
- data/sig/ryac/pipeline/char_shorten.rbs +11 -0
- data/sig/ryac/pipeline/compactor.rbs +125 -0
- data/sig/ryac/pipeline/concatenator.rbs +28 -0
- data/sig/ryac/pipeline/constant_aliaser.rbs +35 -0
- data/sig/ryac/pipeline/constant_fold.rbs +20 -0
- data/sig/ryac/pipeline/control_flow_simplify.rbs +34 -0
- data/sig/ryac/pipeline/data_types.rbs +151 -0
- data/sig/ryac/pipeline/endless_method.rbs +14 -0
- data/sig/ryac/pipeline/errors.rbs +46 -0
- data/sig/ryac/pipeline/file_collector.rbs +34 -0
- data/sig/ryac/pipeline/method_renamer.rbs +25 -0
- data/sig/ryac/pipeline/paren_optimizer.rbs +16 -0
- data/sig/ryac/pipeline/rename_patcher.rbs +38 -0
- data/sig/ryac/pipeline/source_patcher.rbs +12 -0
- data/sig/ryac/pipeline/spelling_shorten.rbs +14 -0
- data/sig/ryac/pipeline/stage.rbs +28 -0
- data/sig/ryac/pipeline/stage_runner.rbs +18 -0
- data/sig/ryac/pipeline/variable_renamer.rbs +21 -0
- data/sig/ryac/union_find.rbs +17 -0
- data/sig/ryac.rbs +13 -0
- data/sig/shims/env.rbs +19 -0
- data/sig/shims/typeprof/core/ast.rbs +299 -0
- data/sig/shims/typeprof/core/type.rbs +22 -0
- data/sig/shims/typeprof/core.rbs +73 -0
- data/tools/regen_pins.rb +45 -0
- metadata +201 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2bddb3f3efea0d09081975f55f29c156ef437180470e5dc08ef30046dd19b995
|
|
4
|
+
data.tar.gz: 062e95d78fa295320ef0ea800d01aa3dff509e9f2c20b341781a06e9ed60671e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 308731a116d5765d712d85be56b819821345847b21cdb1feb591a34071c865f0073addb6e36de31d21c117138e74b6967068fb1e4e5d897176b4a4edff44d5f6
|
|
7
|
+
data.tar.gz: bcd1419c8b24b9f718e20a0b529636b265ecf531689d0468920f101d10f82e10e5019da28ac183a31fe5fa8c072cbd2f00ff1e9f78b5525b916009adf971ad42
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ahogappa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# ryac
|
|
2
|
+
|
|
3
|
+
**ryac** — minify the word *ruby* and you are left with *ry*; this tool then analyzes and compacts, so: **R**ub**y**, **A**nalyzed & **C**ompacted. Pronounced *ryaku*, like 略 — the Japanese word for "abbreviation".
|
|
4
|
+
|
|
5
|
+
A Ruby code minifier that uses [TypeProf](https://github.com/ruby/typeprof) for type-aware analysis and AST-based transformations to achieve high compression rates while preserving functional equivalence.
|
|
6
|
+
|
|
7
|
+
## Philosophy
|
|
8
|
+
|
|
9
|
+
This project takes an **aggressive optimization** approach. In Ruby, even comment removal requires context awareness — for example, `foo\n# comment\n.to_s` works because the comment connects the method chain, but removing it causes a syntax error. Whether a given comment is safe to remove depends entirely on context, making the boundary between safe and unsafe transformations inherently blurry. Since conservative minification is already difficult in Ruby, we choose to optimize as aggressively as possible. TypeProf's type analysis helps make these transformations more informed, but the goal is always maximum compression.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Multi-file support**: Follows `require_relative` and `autoload` to collect and concatenate dependencies into a single output
|
|
14
|
+
- **Whitespace & comment removal**: Strips all unnecessary whitespace and comments
|
|
15
|
+
- **AST transformations**: Boolean/char shortening, constant folding, control flow simplification, endless methods, parenthesis optimization
|
|
16
|
+
- **Constant aliasing**: Renames user-defined constants and shortens repeated external constant paths
|
|
17
|
+
- **Variable renaming**: Shortens local variables, keyword arguments, instance/class/global variables
|
|
18
|
+
- **Method renaming**: Shortens method names with `send(:sym)` coordination and attr-backed ivar optimization
|
|
19
|
+
- **Method alias shortening**: Replaces long stdlib method names with shorter aliases (e.g., `collect` → `map`)
|
|
20
|
+
- **Dead code elimination**: Removes unreachable code after `return`, `break`, `next`, `raise`
|
|
21
|
+
- **Dynamic code detection**: Disables renaming in scopes containing `eval`, `binding`, `send`, etc.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
gem 'ryac'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Command Line
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Minify a file (follows require_relative automatically)
|
|
35
|
+
bin/ryac path/to/entry.rb
|
|
36
|
+
|
|
37
|
+
# Specify compression level (stable or unstable)
|
|
38
|
+
bin/ryac path/to/entry.rb -c stable
|
|
39
|
+
bin/ryac path/to/entry.rb -c unstable
|
|
40
|
+
|
|
41
|
+
# Write to output file
|
|
42
|
+
bin/ryac path/to/entry.rb -o minified.rb
|
|
43
|
+
|
|
44
|
+
# Write constant aliases to a separate file
|
|
45
|
+
bin/ryac path/to/entry.rb -o minified.rb -a aliases.rb
|
|
46
|
+
|
|
47
|
+
# Multiple entry points
|
|
48
|
+
bin/ryac file1.rb file2.rb
|
|
49
|
+
|
|
50
|
+
# Minify installed gem(s) by name (resolved via Gem::Specification)
|
|
51
|
+
bin/ryac -g rack
|
|
52
|
+
bin/ryac -g rack,rack-session -o bundle.rb
|
|
53
|
+
|
|
54
|
+
# Show version / help
|
|
55
|
+
bin/ryac -v
|
|
56
|
+
bin/ryac -h
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Ruby API
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
require 'ryac'
|
|
63
|
+
|
|
64
|
+
minifier = Ryac::Minifier.new
|
|
65
|
+
result = minifier.call('path/to/entry.rb')
|
|
66
|
+
|
|
67
|
+
puts result.content # minified code
|
|
68
|
+
puts result.preamble # external prefix declarations (e.g., "A=TypeProf::Core::AST")
|
|
69
|
+
puts result.aliases # backward-compat alias declarations (e.g., "MyClass=A")
|
|
70
|
+
puts result.stats.file_count # number of files processed
|
|
71
|
+
puts result.stats.compression_ratio # e.g., 0.44 (56% reduction)
|
|
72
|
+
|
|
73
|
+
# Specify optimization level (:stable or :unstable, default: :stable)
|
|
74
|
+
result = minifier.call('path/to/entry.rb', level: :unstable)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Optimization Levels
|
|
78
|
+
|
|
79
|
+
There are exactly two levels, named for their promise. The default is **`stable`**.
|
|
80
|
+
|
|
81
|
+
| Level | Transformations | Promise |
|
|
82
|
+
|-------|----------------|---------|
|
|
83
|
+
| `stable` | AST compaction and folding, constant/class/module renaming with compatibility aliases, external prefix aliasing, local/keyword/instance/class/global variable renaming | Verified frame-for-frame on a real program (Optcarrot). Sound under closed-world analysis; reflection over *names the program renames* is the caveat. |
|
|
84
|
+
| `unstable` | + Method renaming, attr-backed ivar coordination | A program can defeat method renaming by construction (names inside strings, `eval`'d source, `send(computed)`), so this works only when the program plays along. Verified by self-hosting. |
|
|
85
|
+
|
|
86
|
+
Finer configurations are not levels: the pipeline is built from steps, and callers can pass an explicit stage list in place of a level name (`Minifier#call(path, level: [...stage defs...])`). The unit tests pin each step's behavior through exactly that mechanism.
|
|
87
|
+
|
|
88
|
+
See [`tests/ryac/pipeline/`](tests/ryac/pipeline/) for per-stage transformation examples, and [`tests/ryac/levels/`](tests/ryac/levels/) for end-to-end compression examples.
|
|
89
|
+
|
|
90
|
+
### What the levels are verified against
|
|
91
|
+
|
|
92
|
+
The supported boundary is defined by two programs, both verified in CI:
|
|
93
|
+
|
|
94
|
+
- **[Optcarrot](https://github.com/mame/optcarrot) at `stable`** — the minified emulator matches the original frame-for-frame across the 180-frame demo and three scripted play scenarios (1,820 frames of title menus, piece rotation, pausing and button mashing) ([`tests/test_optcarrot.rb`](tests/test_optcarrot.rb))
|
|
95
|
+
- **This minifier itself at `unstable`** — the minified minifier re-minifies the original source to identical output, and minifying its own output is a byte-identical fixed point ([`tests/test_integration.rb`](tests/test_integration.rb))
|
|
96
|
+
|
|
97
|
+
Whether `unstable` holds for a given program depends on the program. Optcarrot stops at `stable` because it defeats method renaming by construction: it builds its CPU/PPU cores as source strings and `eval`s them, scans that text for `@ivar` names with a regexp, and dispatches through `send(computed_symbol)` — method names survive inside strings, out of reach of static analysis. The sinatra and rubocop suites run in CI as regression canaries on a keyword-only step composition, but they sit outside this boundary and do not define it.
|
|
98
|
+
|
|
99
|
+
## Development
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
bundle install
|
|
103
|
+
|
|
104
|
+
# Run tests (fast, excludes self-hosting)
|
|
105
|
+
rake test
|
|
106
|
+
|
|
107
|
+
# Run all tests including self-hosting
|
|
108
|
+
rake test:all
|
|
109
|
+
|
|
110
|
+
# Run self-hosting test only
|
|
111
|
+
rake test:integration
|
|
112
|
+
|
|
113
|
+
# Run gem integration tests (minifies real gems and runs their test suites)
|
|
114
|
+
rake test:gems
|
|
115
|
+
|
|
116
|
+
# Minify optcarrot at :stable and compare rendered frames against the original
|
|
117
|
+
# (requires gem_tests/optcarrot: git clone https://github.com/mame/optcarrot gem_tests/optcarrot)
|
|
118
|
+
rake test:optcarrot
|
|
119
|
+
|
|
120
|
+
# Show compression ratio on self-hosting
|
|
121
|
+
rake benchmark
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Architecture
|
|
125
|
+
|
|
126
|
+
The minification pipeline:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
FileCollector → Concatenator → StageRunner (Compactor → stage list) → Output
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
1. **FileCollector** — Resolves `require_relative` / `autoload` and collects all source files into a dependency graph
|
|
133
|
+
2. **Concatenator** — Topologically sorts files and concatenates them into a single source
|
|
134
|
+
3. **StageRunner** — Compacts the source (AST rebuilt into minimal whitespace form), then walks the level's ordered stage list. Every stage implements one contract — `needs_analysis?` / `fixpoint?` / `collect(ctx, patches)` / `finish(ctx)` — and phase is list position:
|
|
135
|
+
- **Syntactic stages** (no analysis): `ControlFlowSimplify`, `EndlessMethod`, `ConstantFold`, `BooleanShorten`, `CharShorten` before the rename batch; `ParenOptimizer` after it
|
|
136
|
+
- **Analysis stages**: `ConstantAliaser`, `AttrDeclShorten`, `VariableRenamer` (plus `MethodRenamer` at `unstable`) — consecutive analysis stages form one batch sharing a single TypeProf pass, and the runner rejects a list that would need two
|
|
137
|
+
|
|
138
|
+
## Dependencies
|
|
139
|
+
|
|
140
|
+
- [TypeProf](https://github.com/ruby/typeprof) - Type-aware AST analysis
|
|
141
|
+
- [Prism](https://github.com/ruby/prism) - Syntax validation
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
The gem is available as open source under the terms of the MIT License.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << "lib"
|
|
8
|
+
t.test_files = FileList["tests/**/test_*.rb"].exclude("tests/test_integration.rb", "tests/test_fixtures.rb", "tests/test_gem_minification.rb", "tests/test_optcarrot.rb", "tests/_archive/**/*")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
Rake::TestTask.new("test:all") do |t|
|
|
12
|
+
t.libs << "lib"
|
|
13
|
+
t.test_files = FileList["tests/**/test_*.rb"].exclude("tests/test_fixtures.rb", "tests/test_gem_minification.rb", "tests/test_optcarrot.rb", "tests/_archive/**/*")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Rake::TestTask.new("test:integration") do |t|
|
|
17
|
+
t.libs << "lib"
|
|
18
|
+
t.test_files = FileList["tests/test_integration.rb"]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Rake::TestTask.new("test:gems") do |t|
|
|
22
|
+
t.libs << "lib"
|
|
23
|
+
t.test_files = FileList["tests/test_gem_minification.rb"]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
desc "Minify optcarrot at the supported level and compare rendered frames"
|
|
27
|
+
Rake::TestTask.new("test:optcarrot") do |t|
|
|
28
|
+
t.libs << "lib"
|
|
29
|
+
t.test_files = FileList["tests/test_optcarrot.rb"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
desc "Show self-hosting compression ratio"
|
|
33
|
+
task :benchmark do
|
|
34
|
+
$LOAD_PATH.unshift File.expand_path("lib", __dir__)
|
|
35
|
+
require "ryac"
|
|
36
|
+
|
|
37
|
+
entry_path = File.join(__dir__, "lib", "ryac.rb")
|
|
38
|
+
minifier = Ryac::Minifier.new
|
|
39
|
+
result = minifier.call(entry_path)
|
|
40
|
+
stats = result.stats
|
|
41
|
+
compression = ((1 - stats.compression_ratio) * 100).round(1)
|
|
42
|
+
puts "#{stats.file_count} files: #{stats.original_size} -> #{stats.minified_size} bytes (#{compression}% reduction)"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
task default: :test
|
data/Steepfile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "ryac"
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
require "irb"
|
|
11
|
+
IRB.start(__FILE__)
|
data/bin/ryac
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'optparse'
|
|
5
|
+
require_relative '../lib/ryac'
|
|
6
|
+
|
|
7
|
+
options = {
|
|
8
|
+
output: nil,
|
|
9
|
+
aliases: nil,
|
|
10
|
+
level: nil,
|
|
11
|
+
gems: nil
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
parser = OptionParser.new do |opts|
|
|
15
|
+
opts.banner = "Usage: minify [options] [FILE...]"
|
|
16
|
+
|
|
17
|
+
opts.on("-o", "--output FILE", "Write output to FILE instead of stdout") do |file|
|
|
18
|
+
options[:output] = file
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
opts.on("-a", "--aliases FILE", "Write constant alias declarations to FILE") do |file|
|
|
22
|
+
options[:aliases] = file
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
opts.on("-g", "--gem GEM_NAMES", "Minify installed gem(s) by name (comma-separated)") do |names|
|
|
26
|
+
options[:gems] = names.split(',').map(&:strip).reject(&:empty?)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
opts.on("-c", "--compress LEVEL", "Set compression level (stable or unstable)") do |value|
|
|
30
|
+
begin
|
|
31
|
+
options[:level] = Ryac::Minifier.resolve_level(value)
|
|
32
|
+
rescue ArgumentError => e
|
|
33
|
+
warn "Error: #{e.message}"
|
|
34
|
+
warn opts
|
|
35
|
+
exit 1
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
opts.on("-h", "--help", "Display this help message") do
|
|
40
|
+
puts opts
|
|
41
|
+
exit 0
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
opts.on("-v", "--version", "Display version") do
|
|
45
|
+
puts "ryac version #{Ryac::VERSION}"
|
|
46
|
+
exit 0
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
parser.parse!
|
|
52
|
+
rescue OptionParser::InvalidOption => e
|
|
53
|
+
warn "Error: #{e.message}"
|
|
54
|
+
warn parser
|
|
55
|
+
exit 1
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if options[:gems]
|
|
59
|
+
unless ARGV.empty?
|
|
60
|
+
warn "Error: Cannot specify both --gem and file arguments"
|
|
61
|
+
exit 1
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
if options[:gems].empty?
|
|
65
|
+
warn "Error: No gem names specified"
|
|
66
|
+
exit 1
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
begin
|
|
70
|
+
gem_args = Ryac::GemResolver.minifier_args(options[:gems])
|
|
71
|
+
entry = gem_args[:entry]
|
|
72
|
+
project_root = gem_args[:project_root]
|
|
73
|
+
gem_require_paths = gem_args[:gem_require_paths]
|
|
74
|
+
rescue Ryac::Pipeline::GemNotFoundError => e
|
|
75
|
+
warn "Error: #{e.message}"
|
|
76
|
+
exit 1
|
|
77
|
+
end
|
|
78
|
+
else
|
|
79
|
+
if ARGV.empty?
|
|
80
|
+
warn "Error: No entry file specified"
|
|
81
|
+
warn parser
|
|
82
|
+
exit 1
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
ARGV.each do |file|
|
|
86
|
+
unless File.exist?(file)
|
|
87
|
+
warn "Error: File not found: #{file}"
|
|
88
|
+
exit 1
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
entry = ARGV.size == 1 ? ARGV[0] : ARGV.dup
|
|
93
|
+
project_root = nil
|
|
94
|
+
gem_require_paths = []
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
gem_names = options[:gems] || []
|
|
98
|
+
|
|
99
|
+
begin
|
|
100
|
+
minifier = Ryac::Minifier.new
|
|
101
|
+
level_opts = options[:level] ? { level: options[:level] } : {}
|
|
102
|
+
result = minifier.call(entry, **level_opts, project_root: project_root, gem_names: gem_names, gem_require_paths: gem_require_paths)
|
|
103
|
+
|
|
104
|
+
content = if options[:aliases]
|
|
105
|
+
File.write(options[:aliases], result.aliases)
|
|
106
|
+
result.content
|
|
107
|
+
else
|
|
108
|
+
result.full_content
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
if options[:output]
|
|
112
|
+
File.write(options[:output], content)
|
|
113
|
+
else
|
|
114
|
+
puts content
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
warn "Compression: #{((1 - result.stats.compression_ratio) * 100).round(1)}%"
|
|
118
|
+
warn "Files processed: #{result.stats.file_count}"
|
|
119
|
+
rescue Ryac::SyntaxError => e
|
|
120
|
+
warn "Error: the input does not parse"
|
|
121
|
+
warn " #{e.message}"
|
|
122
|
+
exit 2
|
|
123
|
+
rescue Ryac::InputError => e
|
|
124
|
+
warn "Error: #{e.message}"
|
|
125
|
+
exit 2
|
|
126
|
+
rescue Ryac::InternalError => e
|
|
127
|
+
warn "Error: this is a bug in ryac, not in your program — please report it"
|
|
128
|
+
warn " #{e.message}"
|
|
129
|
+
exit 3
|
|
130
|
+
rescue => e
|
|
131
|
+
warn "Error: this is a bug in ryac, not in your program — please report it"
|
|
132
|
+
warn " #{e.class}: #{e.message}"
|
|
133
|
+
exit 3
|
|
134
|
+
end
|