railbow 0.1.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/CHANGELOG.md +31 -0
- data/LICENSE.txt +21 -0
- data/README.md +243 -0
- data/exe/railbow +110 -0
- data/lib/railbow/about_formatter.rb +37 -0
- data/lib/railbow/color_assigner.rb +53 -0
- data/lib/railbow/config.rb +113 -0
- data/lib/railbow/demo/fixtures.rb +121 -0
- data/lib/railbow/demo/migrate_demo.rb +35 -0
- data/lib/railbow/demo/routes_demo.rb +94 -0
- data/lib/railbow/demo/runner.rb +51 -0
- data/lib/railbow/demo/status_demo.rb +164 -0
- data/lib/railbow/formatters/base.rb +195 -0
- data/lib/railbow/git_utils.rb +29 -0
- data/lib/railbow/init.rb +112 -0
- data/lib/railbow/logo.rb +27 -0
- data/lib/railbow/migration_formatter.rb +64 -0
- data/lib/railbow/migration_parser.rb +87 -0
- data/lib/railbow/name_collision_resolver.rb +155 -0
- data/lib/railbow/name_formatter.rb +82 -0
- data/lib/railbow/notes_formatter.rb +311 -0
- data/lib/railbow/params.rb +229 -0
- data/lib/railbow/railtie.rb +44 -0
- data/lib/railbow/routes_formatter.rb +163 -0
- data/lib/railbow/stats_formatter.rb +99 -0
- data/lib/railbow/table/column.rb +24 -0
- data/lib/railbow/table/renderer.rb +441 -0
- data/lib/railbow/table/theme.rb +54 -0
- data/lib/railbow/table.rb +5 -0
- data/lib/railbow/tasks/init.rake +10 -0
- data/lib/railbow/tasks/migrate_status.rake +694 -0
- data/lib/railbow/tasks/stats.rake +85 -0
- data/lib/railbow/text_utils.rb +30 -0
- data/lib/railbow/version.rb +5 -0
- data/lib/railbow.rb +22 -0
- data/sig/railbow.rbs +4 -0
- metadata +137 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 783c8ab9f75f889cd1c54cd36dbc258e06b411b9ae5132af6815564f83d73312
|
|
4
|
+
data.tar.gz: 1200e9edbd432786044d0ac0992773de513390e787500eecd48dc745a473da4e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: be4c900efd96d11e1d896eb0facd145a928fc93b2b08d30b02cf14a3638e0cf5f133f0c929d037cf828cb3862387ad63725cbbc1cdad4fb84afa353b77ef32fe
|
|
7
|
+
data.tar.gz: 5231639b5beaefce4ee54e9d0e81b0309d8370b66e4cb7ca9a0e496ad64d0bebcd8b8a69458fe056789947a216e6b6cbdd3c8ad0fb442ddfebf38d25cbf196b8
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-07-08
|
|
9
|
+
|
|
10
|
+
First public release.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Colorful `db:migrate` / `db:migrate:down` output with readable millisecond
|
|
15
|
+
timing.
|
|
16
|
+
- Rich `db:migrate:status` dashboard: calendar view with month/week
|
|
17
|
+
separators, status aliases, created-at timestamps, landing-date badges,
|
|
18
|
+
branch badges, affected-table detection, time filtering, and highlighting
|
|
19
|
+
of your own migrations.
|
|
20
|
+
- Git integration: authors, diffs, branch origin, landing dates, and
|
|
21
|
+
uncommitted-file indicators.
|
|
22
|
+
- Ghost migration recovery in `db:migrate:status` via the optional
|
|
23
|
+
[mighost](https://github.com/amberpixels/mighost) gem (`Mighost::API`).
|
|
24
|
+
- Color-coded `rails routes`, `rails stats` tables, `rails notes` with git
|
|
25
|
+
blame, and polished `rails about`.
|
|
26
|
+
- Layered configuration: built-in defaults, `~/.config/railbow/config.yml`,
|
|
27
|
+
`.railbow.yml`, `.railbow.local.yml`, and `RBW_*` environment variables;
|
|
28
|
+
interactive `railbow init` / `rake railbow:init` generator.
|
|
29
|
+
- `railbow` CLI wrapper to use Railbow without touching a project's Gemfile,
|
|
30
|
+
plus `railbow demo` showcase.
|
|
31
|
+
- Smart auto-disable in CI, piped output, `NO_COLOR`, and LLM agents.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eugene
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.svg" alt="Railbow" width="580">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<div align="center">
|
|
6
|
+
|
|
7
|
+
[](https://rubygems.org/gems/railbow)
|
|
8
|
+
[](https://github.com/amberpixels/railbow/actions/workflows/ci.yml)
|
|
9
|
+
[](LICENSE.txt)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
**Make your Rails CLI output beautiful.** Railbow enhances migrations, routes, stats, notes, and more with colorful, emoji-rich, information-dense formatting.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Migrations** - colorful `db:migrate` output with readable millisecond timing
|
|
18
|
+
- **Migration Status** - rich `db:migrate:status` with git authors, calendar view, table detection, landing dates, and time filtering
|
|
19
|
+
- **Routes** - color-coded HTTP verbs and highlighted parameters in `rails routes`
|
|
20
|
+
- **Stats** - beautiful `rails stats` tables
|
|
21
|
+
- **Notes** - `rails notes` with git blame, author colors, date filtering, and sorting
|
|
22
|
+
- **About** - polished `rails about` output
|
|
23
|
+
- **Git Integration** - authors, diffs, branch origin, landing dates, uncommitted file indicators
|
|
24
|
+
- **Calendar View** - month separators and week tick markers for migration timelines
|
|
25
|
+
- **Ghost Recovery** - pairs with the optional [mighost](https://github.com/amberpixels/mighost) gem to recover names, authors, and branch badges for `NO FILE` migrations
|
|
26
|
+
- **Smart Defaults** - auto-disables in CI, piped output, `NO_COLOR`, and LLM agents
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
### Option A: Add to your Gemfile (recommended)
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
gem "railbow", group: :development
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
bundle install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Option B: Use the CLI wrapper (no Gemfile changes)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
gem install railbow
|
|
44
|
+
railbow rake db:migrate:status
|
|
45
|
+
railbow rails routes
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The CLI creates a temporary wrapper so Railbow loads automatically without modifying your project's Gemfile.
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
Railbow works automatically once installed. Every example below works with both `rails` and `rake` commands.
|
|
53
|
+
|
|
54
|
+
### `rails db:migrate`
|
|
55
|
+
|
|
56
|
+
**Before:**
|
|
57
|
+
```
|
|
58
|
+
== CreateProducts: migrating =================================================
|
|
59
|
+
-- create_table(:products)
|
|
60
|
+
-> 0.0028s
|
|
61
|
+
== CreateProducts: migrated (0.0028s) ========================================
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**After:**
|
|
65
|
+
```
|
|
66
|
+
🚀 CreateProducts: migrating...
|
|
67
|
+
✓ create_table(:products) → 2.8ms
|
|
68
|
+
✅ CreateProducts: migrated (2.8ms total)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### `rails db:migrate:status`
|
|
72
|
+
|
|
73
|
+
This is Railbow's flagship feature. The plain Rails output:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Status Migration ID Migration Name
|
|
77
|
+
--------------------------------------------------
|
|
78
|
+
up 20260202145343 Add weight field to animals
|
|
79
|
+
up 20260203134528 Create vaccination records
|
|
80
|
+
up 20260210183902 Create pet tags
|
|
81
|
+
up 20260303120000 Add breed restrictions to adoption policies
|
|
82
|
+
up 20260313132325 Create veterinary appointments
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
becomes a rich, information-dense dashboard (with default config):
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
Feb 2026 W06
|
|
89
|
+
↑↑ │ 20260202145343 │ 2026-02-02 14:53:43 │ Add weight field to animals ● animals
|
|
90
|
+
↑↑ │ 20260203134528 │ 2026-02-03 13:45:28 │ Create vaccination records ● vaccination_records
|
|
91
|
+
↑↑ │ 20260210183902 │ 2026-02-10 18:39:02 │ Create pet tags ⤻ Mar 04 ● pet_tags
|
|
92
|
+
───────┼────────────────┼─────────────────────┼──────────────────────────────
|
|
93
|
+
Mar 2026 W10
|
|
94
|
+
↑↑ │ 20260303120000 │ 2026-03-03 12:00:00 │ Add breed restrictions to… ⤻ Mar 13 ● adoption_policies
|
|
95
|
+
↑↑ │ 20260313132325 │ 2026-03-13 13:23:25 │ Create veterinary appointm… ⎇ PS-142 ● veterinary_appointments
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Out of the box you get:
|
|
99
|
+
|
|
100
|
+
- **Calendar separators** — month + ISO week headers to orient you in time
|
|
101
|
+
- **Status aliases** — `↑↑` / `↓↓` instead of `up` / `down` (customizable)
|
|
102
|
+
- **Created At** — timestamp parsed from the migration ID
|
|
103
|
+
- **Landing dates** — `⤻ Mar 04` badge when a migration was merged to main after its creation
|
|
104
|
+
- **Branch badges** — `⎇ PS-142` showing the source branch/ticket
|
|
105
|
+
- **Affected tables** — color-coded table names extracted from migration files
|
|
106
|
+
- **Time filtering** — only the last 70 days shown by default (`since: 70d`)
|
|
107
|
+
- **Your migrations highlighted** — rows authored by you are visually distinct
|
|
108
|
+
|
|
109
|
+
If the [mighost](https://github.com/amberpixels/mighost) gem is installed, migrations whose files were deleted (e.g. after switching branches) show up with a 👻 status and their recovered name and origin branch instead of a bare `********** NO FILE **********` row.
|
|
110
|
+
|
|
111
|
+
### `rails db:migrate:down`
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
⏪ CreateProducts: reverting...
|
|
115
|
+
✓ drop_table(:products) → 1.2ms
|
|
116
|
+
✅ CreateProducts: reverted (1.2ms total)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `rails routes`
|
|
120
|
+
|
|
121
|
+
HTTP verbs are color-coded (GET=green, POST=yellow, PATCH/PUT=cyan, DELETE=red), route parameters (`:id`, `*splat`) are highlighted, and controller#action pairs stand out.
|
|
122
|
+
|
|
123
|
+
### `rails stats`
|
|
124
|
+
|
|
125
|
+
Code statistics rendered as a colorful table with highlighted totals and code-to-test ratio.
|
|
126
|
+
|
|
127
|
+
### `rails notes`
|
|
128
|
+
|
|
129
|
+
Annotations enriched with git blame data — author names, commit dates, and color-coded tags (TODO=yellow, FIXME=red, OPTIMIZE=cyan, HACK=red, NOTE=green).
|
|
130
|
+
|
|
131
|
+
## Configuration
|
|
132
|
+
|
|
133
|
+
Railbow works with zero configuration, but everything is customizable.
|
|
134
|
+
|
|
135
|
+
### Config files
|
|
136
|
+
|
|
137
|
+
Config is loaded in layers (each overrides the previous):
|
|
138
|
+
|
|
139
|
+
1. **Built-in defaults**
|
|
140
|
+
2. **Global:** `~/.config/railbow/config.yml`
|
|
141
|
+
3. **Project:** `.railbow.yml` (commit to git)
|
|
142
|
+
4. **Local:** `.railbow.local.yml` (gitignored, personal overrides)
|
|
143
|
+
|
|
144
|
+
Generate a config interactively:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
railbow init
|
|
148
|
+
# or, within a Rails project:
|
|
149
|
+
rake railbow:init
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Example `.railbow.yml`
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
since: 70d
|
|
156
|
+
date: rel
|
|
157
|
+
git: "author:me,diff,mask:auto"
|
|
158
|
+
view: "calendar,tables"
|
|
159
|
+
calendar: wticks
|
|
160
|
+
|
|
161
|
+
compact: "maxw:120"
|
|
162
|
+
|
|
163
|
+
aliases:
|
|
164
|
+
columns:
|
|
165
|
+
Status: Live
|
|
166
|
+
values:
|
|
167
|
+
Status:
|
|
168
|
+
up: "↑↑"
|
|
169
|
+
down: "↓↓"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Environment variables
|
|
173
|
+
|
|
174
|
+
Every option can also be set via `RBW_*` environment variables, which override config files:
|
|
175
|
+
|
|
176
|
+
| Variable | Example | Description |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `RBW_PLAIN` | `1` | Disable all formatting |
|
|
179
|
+
| `RBW_SINCE` | `2mo`, `70d`, `1y`, `all` | Filter migrations by time period |
|
|
180
|
+
| `RBW_DATE` | `full`, `rel`, `short`, `custom(%b %d)` | Date display format |
|
|
181
|
+
| `RBW_GIT` | `author:me,diff,mask:auto` | Git integration options |
|
|
182
|
+
| `RBW_VIEW` | `calendar,tables` | Enable calendar view and table detection |
|
|
183
|
+
| `RBW_CALENDAR` | `wticks` | Show week tick markers |
|
|
184
|
+
| `RBW_COMPACT` | `oneline,dense,noheader,maxw:80` | Compact display options |
|
|
185
|
+
| `RBW_VERB` | `GET,POST` | Filter routes by HTTP method |
|
|
186
|
+
| `RBW_SORT` | `file`, `date` | Sort order for notes |
|
|
187
|
+
| `RBW_HELP` | `1` | Show help messages |
|
|
188
|
+
|
|
189
|
+
### Quick examples
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# Last 2 months, calendar view
|
|
193
|
+
RBW_SINCE=2mo rake db:migrate:status
|
|
194
|
+
|
|
195
|
+
# Relative dates, highlight your migrations
|
|
196
|
+
RBW_DATE=rel RBW_GIT=author:me rake db:migrate:status
|
|
197
|
+
|
|
198
|
+
# Full git context with table detection
|
|
199
|
+
RBW_GIT=author:all,diff RBW_VIEW=tables rake db:migrate:status
|
|
200
|
+
|
|
201
|
+
# Only GET routes
|
|
202
|
+
RBW_VERB=GET rails routes
|
|
203
|
+
|
|
204
|
+
# Notes sorted by commit date
|
|
205
|
+
RBW_SORT=date rails notes
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## How It Works
|
|
209
|
+
|
|
210
|
+
Railbow integrates through a Rails Railtie — it prepends formatter modules onto existing Rails classes without modifying your code:
|
|
211
|
+
|
|
212
|
+
- `ActiveRecord::Migration` - migration output
|
|
213
|
+
- `ActiveRecord::Tasks::DatabaseTasks` - migration status
|
|
214
|
+
- `ActionDispatch::Routing::ConsoleFormatter::Sheet` - routes
|
|
215
|
+
- `Rails::Info` - about
|
|
216
|
+
- `Rails::SourceAnnotationExtractor` - notes
|
|
217
|
+
|
|
218
|
+
Formatting auto-disables when:
|
|
219
|
+
- `RBW_PLAIN=1` or `NO_COLOR` is set
|
|
220
|
+
- Running in CI (`CI` env var)
|
|
221
|
+
- Output is piped or redirected (non-TTY)
|
|
222
|
+
- Running inside an LLM agent (`CLAUDECODE` env var)
|
|
223
|
+
|
|
224
|
+
## Requirements
|
|
225
|
+
|
|
226
|
+
- Ruby >= 3.1.0
|
|
227
|
+
- Rails >= 7.2 (including 8.x)
|
|
228
|
+
|
|
229
|
+
## Development
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
bin/setup # Install dependencies
|
|
233
|
+
bundle exec rake # Run tests + linting (RSpec + Standard)
|
|
234
|
+
bin/console # Interactive prompt
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Contributing
|
|
238
|
+
|
|
239
|
+
Bug reports and pull requests are welcome at [github.com/amberpixels/railbow](https://github.com/amberpixels/railbow).
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
MIT License. See [LICENSE.txt](https://github.com/amberpixels/railbow/blob/main/LICENSE.txt).
|
data/exe/railbow
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../lib/railbow/version"
|
|
5
|
+
require_relative "../lib/railbow/logo"
|
|
6
|
+
|
|
7
|
+
module Railbow
|
|
8
|
+
module CLI
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def run(args)
|
|
12
|
+
case args.first
|
|
13
|
+
when "--help", "-h", nil
|
|
14
|
+
Railbow.print_logo
|
|
15
|
+
puts
|
|
16
|
+
print_usage
|
|
17
|
+
exit 0
|
|
18
|
+
when "--version", "-v"
|
|
19
|
+
Railbow.print_logo
|
|
20
|
+
puts " v#{Railbow::VERSION}"
|
|
21
|
+
exit 0
|
|
22
|
+
when "init"
|
|
23
|
+
require "railbow/init"
|
|
24
|
+
Railbow::Init.run
|
|
25
|
+
exit 0
|
|
26
|
+
when "demo"
|
|
27
|
+
require_relative "../lib/railbow/demo/runner"
|
|
28
|
+
Railbow::Demo::Runner.run(args[1] || "status")
|
|
29
|
+
exit 0
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
project_gemfile = find_gemfile
|
|
33
|
+
unless project_gemfile
|
|
34
|
+
warn "Error: Could not find a Gemfile in the current directory or any parent directory."
|
|
35
|
+
exit 1
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
wrapper_path = create_wrapper_gemfile(project_gemfile)
|
|
39
|
+
symlink_lockfile(project_gemfile, wrapper_path)
|
|
40
|
+
|
|
41
|
+
env = {"BUNDLE_GEMFILE" => wrapper_path}
|
|
42
|
+
Kernel.exec(env, "bundle", "exec", *args)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def find_gemfile
|
|
46
|
+
dir = Dir.pwd
|
|
47
|
+
loop do
|
|
48
|
+
gemfile = File.join(dir, "Gemfile")
|
|
49
|
+
return gemfile if File.exist?(gemfile)
|
|
50
|
+
|
|
51
|
+
parent = File.dirname(dir)
|
|
52
|
+
return nil if parent == dir
|
|
53
|
+
|
|
54
|
+
dir = parent
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def create_wrapper_gemfile(project_gemfile)
|
|
59
|
+
require "tempfile"
|
|
60
|
+
|
|
61
|
+
wrapper = Tempfile.new(["railbow-wrapper-", ".gemfile"], "/tmp")
|
|
62
|
+
wrapper.write(<<~RUBY)
|
|
63
|
+
eval_gemfile #{project_gemfile.inspect}
|
|
64
|
+
gem "railbow", "= #{Railbow::VERSION}"
|
|
65
|
+
RUBY
|
|
66
|
+
wrapper.close
|
|
67
|
+
|
|
68
|
+
wrapper.path
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def symlink_lockfile(project_gemfile, wrapper_path)
|
|
72
|
+
lockfile = "#{project_gemfile}.lock"
|
|
73
|
+
return unless File.exist?(lockfile)
|
|
74
|
+
|
|
75
|
+
wrapper_lock = "#{wrapper_path}.lock"
|
|
76
|
+
File.symlink(lockfile, wrapper_lock)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def print_usage
|
|
80
|
+
puts <<~HELP
|
|
81
|
+
Usage: railbow <command> [args...]
|
|
82
|
+
|
|
83
|
+
Wraps any bundled Rails command so railbow is loaded automatically,
|
|
84
|
+
without modifying the project's Gemfile.
|
|
85
|
+
|
|
86
|
+
Commands:
|
|
87
|
+
init Generate a config file (interactive)
|
|
88
|
+
demo [type] See railbow in action without a Rails project
|
|
89
|
+
Types: status (default), migrate, routes, all
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
railbow demo
|
|
93
|
+
railbow demo routes
|
|
94
|
+
railbow init
|
|
95
|
+
railbow rake db:migrate:status
|
|
96
|
+
railbow rails db:migrate:status
|
|
97
|
+
railbow rake db:migrate
|
|
98
|
+
|
|
99
|
+
Options:
|
|
100
|
+
--help, -h Show this help message
|
|
101
|
+
--version, -v Show version
|
|
102
|
+
|
|
103
|
+
For daily use, add railbow to your Gemfile instead:
|
|
104
|
+
gem "railbow", group: :development
|
|
105
|
+
HELP
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
Railbow::CLI.run(ARGV)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "formatters/base"
|
|
4
|
+
require_relative "config"
|
|
5
|
+
require_relative "table"
|
|
6
|
+
|
|
7
|
+
module Railbow
|
|
8
|
+
module AboutFormatter
|
|
9
|
+
def to_s
|
|
10
|
+
return super if Railbow.plain?
|
|
11
|
+
|
|
12
|
+
formatter = Formatters::Base.new
|
|
13
|
+
|
|
14
|
+
columns = [
|
|
15
|
+
Table::Column.new(label: "Property", sticky: true),
|
|
16
|
+
Table::Column.new(label: "Value")
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
rows = @@properties.map do |(name, value)|
|
|
20
|
+
val = value.respond_to?(:call) ? value.call : value
|
|
21
|
+
[formatter.bold(name), val.to_s]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
renderer = Table::Renderer.new(
|
|
25
|
+
columns: columns,
|
|
26
|
+
theme: Table::Themes::PLAIN,
|
|
27
|
+
compact: Railbow::Params.compact_options,
|
|
28
|
+
aliases: Railbow::Config.table_aliases
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
output = +"\n"
|
|
32
|
+
output << renderer.render(rows)
|
|
33
|
+
output << "\n"
|
|
34
|
+
output
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Railbow
|
|
4
|
+
# Assigns maximally-spaced colors to a set of unique labels.
|
|
5
|
+
#
|
|
6
|
+
# Instead of hashing into a fixed palette (where two labels can collide
|
|
7
|
+
# onto adjacent colors), this assigns colors by rank — evenly spacing
|
|
8
|
+
# hues around the color wheel. Two labels always get maximally different
|
|
9
|
+
# colors, three labels get ~120 deg apart, etc.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# assigner = Railbow::ColorAssigner.new(["Alice", "Bob", "Carol"])
|
|
13
|
+
# assigner.color_for("Alice") # => "\e[38;5;174m"
|
|
14
|
+
# assigner.code_for("Bob") # => 116 (256-color code)
|
|
15
|
+
#
|
|
16
|
+
class ColorAssigner
|
|
17
|
+
# Soft/muted 256-color codes arranged around the hue wheel.
|
|
18
|
+
# 12 stops, evenly spaced in hue, all at moderate saturation/lightness
|
|
19
|
+
# so they look pleasant on dark terminals.
|
|
20
|
+
WHEEL = [
|
|
21
|
+
174, # 0° coral / muted rose
|
|
22
|
+
216, # 30° peach
|
|
23
|
+
180, # 60° warm sand
|
|
24
|
+
150, # 120° sage green
|
|
25
|
+
116, # 160° soft teal
|
|
26
|
+
117, # 200° soft sky blue
|
|
27
|
+
111, # 220° periwinkle
|
|
28
|
+
146, # 260° lavender
|
|
29
|
+
176, # 300° soft magenta
|
|
30
|
+
182 # 330° dusty pink
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
def initialize(labels)
|
|
34
|
+
sorted = labels.uniq.sort
|
|
35
|
+
@mapping = {}
|
|
36
|
+
sorted.each_with_index do |label, idx|
|
|
37
|
+
wheel_idx = (idx * WHEEL.size / [sorted.size, 1].max) % WHEEL.size
|
|
38
|
+
@mapping[label] = WHEEL[wheel_idx]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns the 256-color ANSI escape for the given label.
|
|
43
|
+
def color_for(label)
|
|
44
|
+
code = code_for(label)
|
|
45
|
+
"\e[38;5;#{code}m"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Returns just the 256-color code integer for the given label.
|
|
49
|
+
def code_for(label)
|
|
50
|
+
@mapping[label] || WHEEL[0]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Railbow
|
|
6
|
+
module Config
|
|
7
|
+
GEM_DEFAULTS = {
|
|
8
|
+
"aliases" => {
|
|
9
|
+
"columns" => {
|
|
10
|
+
"Status" => "Live"
|
|
11
|
+
},
|
|
12
|
+
"values" => {
|
|
13
|
+
"Status" => {"up" => "↑↑", "down" => "↓↓"}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"since" => "70d",
|
|
17
|
+
"git" => "author:all,diff,mask:auto",
|
|
18
|
+
"author_format" => "short",
|
|
19
|
+
"view" => "calendar,tables",
|
|
20
|
+
"calendar" => "wticks"
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
@root = nil
|
|
24
|
+
@loaded = nil
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
# Overridable root for config file lookup. Defaults to Rails.root or Dir.pwd.
|
|
29
|
+
def root
|
|
30
|
+
@root || ((defined?(Rails) && Rails.respond_to?(:root) && Rails.root) ? Rails.root.to_s : Dir.pwd)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def root=(path)
|
|
34
|
+
@root = path
|
|
35
|
+
@loaded = nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def reset!
|
|
39
|
+
@root = nil
|
|
40
|
+
@loaded = nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def global_dir
|
|
44
|
+
xdg = ENV["XDG_CONFIG_HOME"]
|
|
45
|
+
base = (xdg && !xdg.empty?) ? xdg : File.join(Dir.home, ".config")
|
|
46
|
+
File.join(base, "railbow")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def config_files
|
|
50
|
+
files = []
|
|
51
|
+
|
|
52
|
+
# 1. Global
|
|
53
|
+
global = File.join(global_dir, "config.yml")
|
|
54
|
+
files << global if File.exist?(global)
|
|
55
|
+
|
|
56
|
+
# 2. Project .railbow.yml / .railbow.yaml
|
|
57
|
+
%w[.railbow.yml .railbow.yaml].each do |name|
|
|
58
|
+
full = File.join(root, name)
|
|
59
|
+
if File.exist?(full)
|
|
60
|
+
files << full
|
|
61
|
+
break
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# 3. Local .railbow.local.yml / .railbow.local.yaml
|
|
66
|
+
%w[.railbow.local.yml .railbow.local.yaml].each do |name|
|
|
67
|
+
full = File.join(root, name)
|
|
68
|
+
if File.exist?(full)
|
|
69
|
+
files << full
|
|
70
|
+
break
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
files
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def load
|
|
78
|
+
@loaded ||= config_files.reduce(GEM_DEFAULTS.dup) { |acc, path| deep_merge(acc, read_yaml(path)) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def column_aliases
|
|
82
|
+
load.dig("aliases", "columns") || {}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def value_aliases
|
|
86
|
+
load.dig("aliases", "values") || {}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def table_aliases
|
|
90
|
+
{columns: column_aliases, values: value_aliases}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def read_yaml(path)
|
|
94
|
+
content = begin
|
|
95
|
+
YAML.safe_load_file(path)
|
|
96
|
+
rescue Psych::SyntaxError, Psych::DisallowedClass
|
|
97
|
+
warn " Warning: failed to parse #{path}, using defaults"
|
|
98
|
+
{}
|
|
99
|
+
end
|
|
100
|
+
content.is_a?(Hash) ? content : {}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def deep_merge(base, override)
|
|
104
|
+
base.merge(override) do |_key, old_val, new_val|
|
|
105
|
+
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
|
106
|
+
deep_merge(old_val, new_val)
|
|
107
|
+
else
|
|
108
|
+
new_val
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|