railbow 0.6.2 → 0.7.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 +4 -4
- data/CHANGELOG.md +58 -0
- data/README.md +30 -6
- data/lib/railbow/about_formatter.rb +57 -21
- data/lib/railbow/calendar.rb +2 -4
- data/lib/railbow/clock.rb +72 -0
- data/lib/railbow/color_assigner.rb +6 -12
- data/lib/railbow/config.rb +1 -1
- data/lib/railbow/demo/status_demo.rb +4 -11
- data/lib/railbow/formatters/base.rb +11 -32
- data/lib/railbow/init.rb +5 -1
- data/lib/railbow/migration_formatter.rb +41 -25
- data/lib/railbow/migration_parser.rb +12 -19
- data/lib/railbow/multi_db.rb +15 -1
- data/lib/railbow/name_collision_resolver.rb +14 -27
- data/lib/railbow/name_formatter.rb +15 -16
- data/lib/railbow/notes_formatter.rb +42 -19
- data/lib/railbow/params.rb +33 -27
- data/lib/railbow/railtie.rb +8 -6
- data/lib/railbow/routes_formatter.rb +18 -6
- data/lib/railbow/stats_formatter.rb +139 -69
- data/lib/railbow/status/ghosts.rb +5 -6
- data/lib/railbow/status/git_data.rb +11 -18
- data/lib/railbow/status/help.rb +6 -0
- data/lib/railbow/status/printer.rb +19 -33
- data/lib/railbow/status/section.rb +28 -44
- data/lib/railbow/table/renderer.rb +27 -35
- data/lib/railbow/tasks/migrate_status.rake +33 -17
- data/lib/railbow/tasks/stats.rake +11 -75
- data/lib/railbow/version.rb +1 -1
- data/lib/railbow.rb +28 -4
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cdfe578f284175c30e3b9f4c77a83f62b8e63b8019e575590f8ad471e5639efb
|
|
4
|
+
data.tar.gz: 5874946340d18626a566b0190f734f6a94a55a3d40dfb0777760ab377b9d295d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3fc62787e1bfd0913be00ca297c5c9210a312b067c68960c13699298a3a064e396a20bc952e65544d19059a03c02454c99413b8f59fd84e6890d9145e8ba03c1
|
|
7
|
+
data.tar.gz: e2253cf2117f5ed4a6702f9500774789d05007876f09ad043b014f9e7b0d53f9616ea21fc32247463081e131aa533b2bf149cb65be99f8f08b37478a281e22ee
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,64 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.0] - 2026-10-09
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `RBW_TZ` (or `tz:` in `.railbow.yml`) picks the timezone railbow shows
|
|
13
|
+
dates in: `local` (the default), `utc`, or any zone Rails knows, such as
|
|
14
|
+
`America/New_York`. It covers the `db:migrate:status` date column, calendar
|
|
15
|
+
separators and landed badges, `rails notes` blame dates, the `rails about`
|
|
16
|
+
schema version, and where `RBW_SINCE` windows start.
|
|
17
|
+
- A railbow bug no longer breaks the command it decorates. If rendering
|
|
18
|
+
raises, railbow prints a one-line `railbow:` warning to stderr and falls back
|
|
19
|
+
to the stock Rails output for `rails about`, `rails stats`, `rails notes`,
|
|
20
|
+
`rails routes`, migrations and `db:migrate:status` (including multi-database
|
|
21
|
+
runs), then stays plain for the rest of the process. A migration body is
|
|
22
|
+
never re-run by the fallback.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Dates in `db:migrate:status` (and calendar separators) are now shown in
|
|
27
|
+
local time. They used to repeat the UTC digits of the Migration ID, so on a
|
|
28
|
+
machine outside UTC the column shifts by the UTC offset. Set `tz: utc` (or
|
|
29
|
+
`RBW_TZ=utc`) for the previous display.
|
|
30
|
+
- `rails about` now says something the stock output does not. The environment
|
|
31
|
+
is colored (production in bold red, development green, anything else
|
|
32
|
+
yellow), the database schema version shows its time and age, version
|
|
33
|
+
numbers share one accent color, the application root steps back, and list
|
|
34
|
+
values such as Middleware get a line per item. The Rails title line is kept,
|
|
35
|
+
and the old "Property / Value" header row is gone.
|
|
36
|
+
- `rails stats` is redrawn in the `db:migrate:status` table style: code and
|
|
37
|
+
test directories under their own headings, thousands grouped, zeros dimmed,
|
|
38
|
+
M/C and LOC/M dimmed for tests, a bar for each directory's share of all LOC,
|
|
39
|
+
and a bold Total row. Directories registered by other gems (such as
|
|
40
|
+
rspec-rails) are included.
|
|
41
|
+
- The default `since` window is now 90 days (was 70), close to three months.
|
|
42
|
+
Set `since: 70d` (or `RBW_SINCE=70d`) to keep the old window.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- `rails routes` no longer leaves a blank line before every piece of a wrapped
|
|
47
|
+
route name when the other columns nearly fill the terminal. A group too wide
|
|
48
|
+
for the terminal drops its Prefix column, and a table never wraps its last
|
|
49
|
+
column into lines that would run past the edge: the value stays on its row.
|
|
50
|
+
- `RBW_DATE=rel` ages are no longer off by the machine's UTC offset.
|
|
51
|
+
Migration IDs are UTC timestamps but were read as local time.
|
|
52
|
+
- `rails about` no longer crashes with `uninitialized class variable
|
|
53
|
+
@@properties in Railbow::AboutFormatter`.
|
|
54
|
+
- `rails stats` is formatted again. Since Rails 8.0 it is a Rails command
|
|
55
|
+
that never loads rake tasks, so railbow's hook never ran, and on every Rails
|
|
56
|
+
version the formatter read the wrong data and printed nothing of its own.
|
|
57
|
+
On Rails 7.2 and 8.0, `rake stats` no longer risks running railbow's
|
|
58
|
+
Rails 8.1 replacement task next to the stock one.
|
|
59
|
+
- `rails notes` no longer prints `fatal: no such path ... in HEAD` for notes
|
|
60
|
+
in untracked or gitignored files (such as `app/assets/builds`), and no
|
|
61
|
+
longer hides those notes as older than the `since` window. They are dated
|
|
62
|
+
as uncommitted, the way `git blame` dates uncommitted lines, and with
|
|
63
|
+
`RBW_GIT=author` they show dim dashes in the author column instead of git's
|
|
64
|
+
"Not Committed Yet" squeezed into a name like "No Y".
|
|
65
|
+
|
|
8
66
|
## [0.6.2] - 2026-09-21
|
|
9
67
|
|
|
10
68
|
### Changed
|
data/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
- **Routes** - color-coded HTTP verbs and highlighted parameters in `rails routes`
|
|
20
20
|
- **Stats** - beautiful `rails stats` tables
|
|
21
21
|
- **Notes** - `rails notes` with git blame, author colors, date filtering, and sorting
|
|
22
|
-
- **About** -
|
|
22
|
+
- **About** - `rails about` with the environment color-coded (production in red) and the schema version shown as a date and age
|
|
23
23
|
- **Git Integration** - authors, diffs, branch origin, landing dates, uncommitted file indicators
|
|
24
24
|
- **Calendar View** - month separators plus week ticks or full week rows, with optional per-section counts
|
|
25
25
|
- **Multiple Databases** - read one table by default, the rest summarized in a line each (and expanded when they have migrations pending); sharded databases merged with a status per shard
|
|
@@ -104,7 +104,7 @@ Out of the box you get:
|
|
|
104
104
|
- **Landing dates** - `⤻ Mar 04` badge when a migration was merged to main after its creation
|
|
105
105
|
- **Branch badges** - `⎇ PS-142` showing the source branch/ticket
|
|
106
106
|
- **Affected tables** - color-coded table names extracted from migration files
|
|
107
|
-
- **Time filtering** - only the last
|
|
107
|
+
- **Time filtering** - only the last 90 days shown by default (`since: 90d`), as a soft limit: never fewer than 10 migrations, however old (`since_min: 10`)
|
|
108
108
|
- **Your migrations highlighted** - rows authored by you are visually distinct
|
|
109
109
|
- **Pending migrations greyed out** - a `down` row keeps its status glyph but loses its colors
|
|
110
110
|
|
|
@@ -197,7 +197,7 @@ rather than an empty table, which is what keeps the `cache`, `queue` and
|
|
|
197
197
|
|
|
198
198
|
```
|
|
199
199
|
⋯ cache · 5 migrations, all applied · latest Feb 12 2024
|
|
200
|
-
⋯ queue · 12 migrations, 3 pending outside the
|
|
200
|
+
⋯ queue · 12 migrations, 3 pending outside the 90d window - RBW_SINCE=all
|
|
201
201
|
```
|
|
202
202
|
|
|
203
203
|
Databases that share a `migrations_paths` (horizontal sharding) run the same
|
|
@@ -236,7 +236,21 @@ HTTP verbs are color-coded (GET=green, POST=yellow, PATCH/PUT=cyan, DELETE=red),
|
|
|
236
236
|
|
|
237
237
|
### `rails stats`
|
|
238
238
|
|
|
239
|
-
Code statistics
|
|
239
|
+
Code statistics in the same table style as `db:migrate:status`, code and tests grouped, with each directory's share of all LOC:
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
Name Lines LOC Classes Methods M/C LOC/M Share of LOC
|
|
243
|
+
Code │ │ │ │ │ │ │
|
|
244
|
+
Controllers │ 57,143 │ 42,693 │ 212 │ 3,472 │ 16 │ 10 │ █████████▊ 22.0%
|
|
245
|
+
Models │ 76,529 │ 48,353 │ 302 │ 4,819 │ 15 │ 8 │ ███████████ 24.9%
|
|
246
|
+
Views │ 98,412 │ 70,120 │ 0 │ 0 │ 0 │ 0 │ ████████████████ 36.1%
|
|
247
|
+
Tests │ │ │ │ │ │ │
|
|
248
|
+
Model specs │ 40,210 │ 33,000 │ 0 │ 120 │ 0 │ 273 │ ███████▌ 17.0%
|
|
249
|
+
│ │ │ │ │ │ │
|
|
250
|
+
Total │ 272,294 │ 194,166 │ 514 │ 8,411 │ 16 │ 21 │
|
|
251
|
+
|
|
252
|
+
Code 161,166 LOC · Tests 33,000 LOC · Code to Test Ratio 1:0.2
|
|
253
|
+
```
|
|
240
254
|
|
|
241
255
|
### `rails notes`
|
|
242
256
|
|
|
@@ -266,7 +280,7 @@ rake railbow:init
|
|
|
266
280
|
### Example `.railbow.yml`
|
|
267
281
|
|
|
268
282
|
```yaml
|
|
269
|
-
since:
|
|
283
|
+
since: 90d
|
|
270
284
|
since_min: 10
|
|
271
285
|
date: rel
|
|
272
286
|
git: "author:me,diff,mask:auto"
|
|
@@ -293,9 +307,10 @@ Every option can also be set via `RBW_*` environment variables, which override c
|
|
|
293
307
|
|---|---|---|
|
|
294
308
|
| `RBW_PLAIN` | `1` | Disable all formatting |
|
|
295
309
|
| `RBW_FORCE` | `1` | Force formatting even when piped, in CI, or run by an LLM agent (`RBW_PLAIN=1` still wins) |
|
|
296
|
-
| `RBW_SINCE` | `2mo`, `
|
|
310
|
+
| `RBW_SINCE` | `2mo`, `90d`, `1y`, `all` | Filter migrations by time period |
|
|
297
311
|
| `RBW_SINCE_MIN` | `10`, `0` | Never show fewer than this many migrations, however old (default 10; `0` disables) |
|
|
298
312
|
| `RBW_DATE` | `full`, `rel`, `short`, `custom(%b %d)` | Date display format |
|
|
313
|
+
| `RBW_TZ` | `local` (default), `utc`, `America/New_York` | Timezone dates are shown in. Migration IDs are UTC; this picks the clock they are displayed on |
|
|
299
314
|
| `RBW_GIT` | `author:me,diff,mask:auto` | Git integration options |
|
|
300
315
|
| `RBW_VIEW` | `calendar,tables` | Enable calendar view and table detection |
|
|
301
316
|
| `RBW_CALENDAR` | `wticks`, `wdividers,counts`, `` (empty) | Week markers: tick marks, full week separator rows, per-section counts. Empty means month separators only |
|
|
@@ -344,6 +359,11 @@ Formatting auto-disables when:
|
|
|
344
359
|
formatted output to a file, or letting an agent inspect the real rendering).
|
|
345
360
|
An explicit `RBW_PLAIN=1` always wins over `RBW_FORCE`.
|
|
346
361
|
|
|
362
|
+
If railbow itself hits an error while rendering, the command still works: it
|
|
363
|
+
prints a one-line `railbow:` warning to stderr naming the error, then shows the
|
|
364
|
+
stock Rails output and stays plain for the rest of the run. Migrations are never
|
|
365
|
+
re-run by this; only their output falls back.
|
|
366
|
+
|
|
347
367
|
## Requirements
|
|
348
368
|
|
|
349
369
|
- Ruby >= 3.1.0
|
|
@@ -357,6 +377,10 @@ bundle exec rake # Run tests + linting (RSpec + Standard)
|
|
|
357
377
|
bin/console # Interactive prompt
|
|
358
378
|
```
|
|
359
379
|
|
|
380
|
+
To see a change in a real app (one that loads this checkout), set `RBW_APP` to
|
|
381
|
+
its path, then `just shot "rails routes"` saves a PNG and
|
|
382
|
+
`just cast "rails routes" 100` a scrolling GIF at 100 columns, in `tmp/shots/`.
|
|
383
|
+
|
|
360
384
|
## Contributing
|
|
361
385
|
|
|
362
386
|
Bug reports and pull requests are welcome at [github.com/amberpixels/railbow](https://github.com/amberpixels/railbow).
|
|
@@ -1,37 +1,73 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "formatters/base"
|
|
4
|
-
require_relative "config"
|
|
5
|
-
require_relative "table"
|
|
6
4
|
|
|
7
5
|
module Railbow
|
|
8
6
|
module AboutFormatter
|
|
7
|
+
TITLE = "About your application's environment"
|
|
8
|
+
|
|
9
9
|
def to_s
|
|
10
10
|
return super if Railbow.plain?
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Railbow.guard(-> { super }) { railbow_to_s }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# Same shape as Rails' own listing (title, then one aligned row per
|
|
18
|
+
# property), with the values styled by what they mean. Properties other
|
|
19
|
+
# gems register render as plain text.
|
|
20
|
+
def railbow_to_s
|
|
21
|
+
f = Formatters::Base.new
|
|
22
|
+
width = properties.map { |(name, _)| name.length }.max.to_i
|
|
23
|
+
lines = [f.bold(TITLE)]
|
|
13
24
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
properties.each do |(name, value)|
|
|
26
|
+
# Lists such as Middleware get a line per item instead of one long run.
|
|
27
|
+
values = Array(value).map { |v| railbow_style_value(f, name, v.to_s) }
|
|
28
|
+
lines << "#{f.bold(name.ljust(width))} #{values.first}"
|
|
29
|
+
values.drop(1).each { |v| lines << "#{" " * width} #{v}" }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
lines.join("\n")
|
|
33
|
+
end
|
|
18
34
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
def railbow_style_value(f, name, value)
|
|
36
|
+
case name
|
|
37
|
+
when "Environment" then railbow_style_environment(f, value)
|
|
38
|
+
when "Application root" then f.dim(value)
|
|
39
|
+
when "Database schema version" then railbow_style_schema_version(f, value)
|
|
40
|
+
when /version\z/i then railbow_style_version(f, value)
|
|
41
|
+
else value
|
|
22
42
|
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Production is the one environment worth a jolt.
|
|
46
|
+
def railbow_style_environment(f, value)
|
|
47
|
+
case value
|
|
48
|
+
when "production" then f.bold(f.red(value))
|
|
49
|
+
when "development" then f.green(value)
|
|
50
|
+
else f.yellow(value)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The version number takes the accent; anything trailing it, like Ruby's
|
|
55
|
+
# build date and platform, steps back.
|
|
56
|
+
def railbow_style_version(f, value)
|
|
57
|
+
match = value.match(/\d+(?:\.\w+)+/)
|
|
58
|
+
return value unless match
|
|
59
|
+
|
|
60
|
+
tail = match.post_match.empty? ? "" : f.dim(match.post_match)
|
|
61
|
+
"#{match.pre_match}#{f.cyan(match[0])}#{tail}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Shown on railbow's clock (RBW_TZ), with the zone named because the
|
|
65
|
+
# digits of the version itself are UTC.
|
|
66
|
+
def railbow_style_schema_version(f, value)
|
|
67
|
+
time = Railbow::Clock.from_version(value)
|
|
68
|
+
return value unless time
|
|
23
69
|
|
|
24
|
-
|
|
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
|
|
70
|
+
"#{f.cyan(value)} #{f.dim("(#{time.strftime("%Y-%m-%d %H:%M %Z")}, #{f.format_relative_time_from(time)})")}"
|
|
35
71
|
end
|
|
36
72
|
end
|
|
37
73
|
end
|
data/lib/railbow/calendar.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "date"
|
|
4
|
+
require_relative "clock"
|
|
4
5
|
|
|
5
6
|
module Railbow
|
|
6
7
|
# Turns an ordered list of migration versions into the calendar furniture that
|
|
@@ -58,10 +59,7 @@ module Railbow
|
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
def parse_date(version)
|
|
61
|
-
|
|
62
|
-
Date.new(v[0..3].to_i, v[4..5].to_i, v[6..7].to_i)
|
|
63
|
-
rescue Date::Error
|
|
64
|
-
nil
|
|
62
|
+
Railbow::Clock.from_version(version)&.to_date
|
|
65
63
|
end
|
|
66
64
|
|
|
67
65
|
def month_key(date)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
|
|
5
|
+
module Railbow
|
|
6
|
+
# The clock railbow shows times on. Migration IDs and git dates are fixed
|
|
7
|
+
# moments; RBW_TZ (or `tz:` in .railbow.yml) only picks the zone they are
|
|
8
|
+
# shown in: "local" (the default), "utc", or any zone name Rails accepts,
|
|
9
|
+
# e.g. "America/New_York" or "Eastern Time (US & Canada)".
|
|
10
|
+
module Clock
|
|
11
|
+
@zones = {}
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# A migration ID is the UTC time the migration was generated, which is
|
|
16
|
+
# how Rails names them. Returns nil for anything that is not one.
|
|
17
|
+
def from_version(version)
|
|
18
|
+
v = version.to_s
|
|
19
|
+
return nil unless v.match?(/\A\d{14}\z/)
|
|
20
|
+
|
|
21
|
+
utc = Time.utc(
|
|
22
|
+
v[0..3].to_i, v[4..5].to_i, v[6..7].to_i,
|
|
23
|
+
v[8..9].to_i, v[10..11].to_i, v[12..13].to_i
|
|
24
|
+
)
|
|
25
|
+
in_zone(utc)
|
|
26
|
+
rescue ArgumentError
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def in_zone(time)
|
|
31
|
+
case (z = zone)
|
|
32
|
+
when :local then time.getlocal
|
|
33
|
+
when :utc then time.getutc
|
|
34
|
+
else time.in_time_zone(z)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def today
|
|
39
|
+
in_zone(Time.now).to_date
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The migration ID of the midnight that starts date on this clock, so a
|
|
43
|
+
# day-based window can be compared against migration IDs directly.
|
|
44
|
+
def version_at(date)
|
|
45
|
+
start = case (z = zone)
|
|
46
|
+
when :local then Time.local(date.year, date.month, date.day)
|
|
47
|
+
when :utc then Time.utc(date.year, date.month, date.day)
|
|
48
|
+
else z.local(date.year, date.month, date.day)
|
|
49
|
+
end
|
|
50
|
+
start.utc.strftime("%Y%m%d%H%M%S")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def zone
|
|
54
|
+
name = Params.tz
|
|
55
|
+
@zones[name] ||= resolve(name)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def resolve(name)
|
|
59
|
+
case name.downcase
|
|
60
|
+
when "local" then :local
|
|
61
|
+
when "utc" then :utc
|
|
62
|
+
else
|
|
63
|
+
require "active_support"
|
|
64
|
+
require "active_support/time"
|
|
65
|
+
ActiveSupport::TimeZone[name] || begin
|
|
66
|
+
warn " Warning: unrecognized RBW_TZ=#{name}, showing local time"
|
|
67
|
+
:local
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Railbow
|
|
4
|
-
#
|
|
4
|
+
# Spreads a set of labels evenly around the color wheel by rank. Hashing
|
|
5
|
+
# into a palette could land two labels on neighboring colors; by rank, two
|
|
6
|
+
# labels sit opposite each other, three about 120 deg apart, and so on.
|
|
5
7
|
#
|
|
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
8
|
# assigner = Railbow::ColorAssigner.new(["Alice", "Bob", "Carol"])
|
|
13
9
|
# assigner.color_for("Alice") # => "\e[38;5;174m"
|
|
14
10
|
# assigner.code_for("Bob") # => 116 (256-color code)
|
|
15
11
|
#
|
|
16
12
|
class ColorAssigner
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# so they look pleasant on dark terminals.
|
|
13
|
+
# Muted 256-color codes in hue order, at moderate saturation and
|
|
14
|
+
# lightness so they sit well on dark terminals.
|
|
20
15
|
WHEEL = [
|
|
21
16
|
174, # 0° coral / muted rose
|
|
22
17
|
216, # 30° peach
|
|
@@ -39,13 +34,12 @@ module Railbow
|
|
|
39
34
|
end
|
|
40
35
|
end
|
|
41
36
|
|
|
42
|
-
# Returns the 256-color ANSI escape for the given label.
|
|
43
37
|
def color_for(label)
|
|
44
38
|
code = code_for(label)
|
|
45
39
|
"\e[38;5;#{code}m"
|
|
46
40
|
end
|
|
47
41
|
|
|
48
|
-
#
|
|
42
|
+
# An unknown label gets the first color.
|
|
49
43
|
def code_for(label)
|
|
50
44
|
@mapping[label] || WHEEL[0]
|
|
51
45
|
end
|
data/lib/railbow/config.rb
CHANGED
|
@@ -31,11 +31,10 @@ module Railbow
|
|
|
31
31
|
git_email = Fixtures::DEMO_USER_EMAIL
|
|
32
32
|
git_name = Fixtures::DEMO_USER_NAME
|
|
33
33
|
|
|
34
|
-
#
|
|
34
|
+
# Decides which landed badges count as fresh.
|
|
35
35
|
latest_version = migrations.last[:version]
|
|
36
36
|
latest_mig_date = parse_version_date(latest_version)
|
|
37
37
|
|
|
38
|
-
# Check if any landed tags exist
|
|
39
38
|
has_landed_tags = migrations.any? { |m|
|
|
40
39
|
next false unless m[:landed_date]
|
|
41
40
|
mig_date = parse_version_date(m[:version])
|
|
@@ -55,13 +54,12 @@ module Railbow
|
|
|
55
54
|
truncate_fn: ->(cell_raw, max_w) { formatter.name_with_tags_fitted(cell_raw, max_w) },
|
|
56
55
|
shrinkable: true, shrink_floor: 24)
|
|
57
56
|
]
|
|
58
|
-
# No
|
|
57
|
+
# No Who column: author:me only highlights.
|
|
59
58
|
|
|
60
59
|
tables_truncate_fn = ->(cell_raw, max_w) { formatter.table_tags_fitted(cell_raw, max_w) }
|
|
61
60
|
table_columns << Railbow::Table::Column.new(label: "Tables", droppable: 1,
|
|
62
61
|
truncate: true, truncate_fn: tables_truncate_fn)
|
|
63
62
|
|
|
64
|
-
# Build rows
|
|
65
63
|
highlight_rows = Set.new
|
|
66
64
|
down_rows = Set.new
|
|
67
65
|
rows = migrations.each_with_index.map do |m, idx|
|
|
@@ -74,10 +72,8 @@ module Railbow
|
|
|
74
72
|
|
|
75
73
|
display_name = m[:name]
|
|
76
74
|
|
|
77
|
-
# Diff tag (branch badge)
|
|
78
75
|
diff_tag = m[:branch] ? formatter.diff_tag_branch(m[:branch]) : nil
|
|
79
76
|
|
|
80
|
-
# Landed badge
|
|
81
77
|
landed_tag = nil
|
|
82
78
|
if m[:landed_date]
|
|
83
79
|
mig_date = parse_version_date(m[:version])
|
|
@@ -87,7 +83,6 @@ module Railbow
|
|
|
87
83
|
end
|
|
88
84
|
end
|
|
89
85
|
|
|
90
|
-
# Append tags to display_name
|
|
91
86
|
tags = [landed_tag, diff_tag].compact.join(" ")
|
|
92
87
|
if !tags.empty? && name_col_width
|
|
93
88
|
tags_width = formatter.display_width(formatter.strip_ansi(tags))
|
|
@@ -102,20 +97,18 @@ module Railbow
|
|
|
102
97
|
|
|
103
98
|
created_at = formatter.format_date(m[:version], date_format)
|
|
104
99
|
|
|
105
|
-
#
|
|
100
|
+
# author:me highlighting.
|
|
106
101
|
if m[:author_email] == git_email ||
|
|
107
102
|
(git_name && m[:author_name] && m[:author_name].downcase == git_name.downcase)
|
|
108
103
|
highlight_rows << idx
|
|
109
104
|
end
|
|
110
105
|
|
|
111
|
-
# Table tags
|
|
112
106
|
table_tags = formatter.table_tags(m[:tables])
|
|
113
107
|
|
|
114
108
|
[colored_status, m[:version], created_at, display_name, table_tags]
|
|
115
109
|
end
|
|
116
110
|
|
|
117
|
-
#
|
|
118
|
-
# tried out here without a Rails project.
|
|
111
|
+
# Real params, so RBW_CALENDAR can be tried without a Rails project.
|
|
119
112
|
calendar = Railbow::Calendar.build(
|
|
120
113
|
migrations.map { |m| m[:version] },
|
|
121
114
|
weeks: Railbow::Params.calendar_wdividers?,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "zlib"
|
|
4
|
+
require_relative "../clock"
|
|
4
5
|
require_relative "../text_utils"
|
|
5
6
|
|
|
6
7
|
module Railbow
|
|
@@ -69,23 +70,20 @@ module Railbow
|
|
|
69
70
|
table_names.map { |t| table_tag(t) }.join(" ")
|
|
70
71
|
end
|
|
71
72
|
|
|
72
|
-
# Re-fits a
|
|
73
|
-
#
|
|
74
|
-
# Accepts the full formatted string (with ANSI) and splits it into segments.
|
|
73
|
+
# Re-fits a formatted table_tags string within max_width: as many whole
|
|
74
|
+
# tags as fit plus "+N" for the rest, then one truncated tag, then "+N".
|
|
75
75
|
def table_tags_fitted(formatted_str, max_width)
|
|
76
76
|
return formatted_str if formatted_str.nil? || formatted_str.empty?
|
|
77
77
|
|
|
78
|
-
#
|
|
78
|
+
# Each tag is "\e[38;5;NNNm● name\e[0m"
|
|
79
79
|
segments = formatted_str.scan(/\e\[38;5;\d+m● [^\e]+\e\[0m/)
|
|
80
80
|
return formatted_str if segments.empty?
|
|
81
81
|
|
|
82
82
|
total = segments.size
|
|
83
83
|
plain_segments = segments.map { |s| strip_ansi(s) }
|
|
84
84
|
|
|
85
|
-
# Try fitting all tags
|
|
86
85
|
return formatted_str if display_width(plain_segments.join(" ")) <= max_width
|
|
87
86
|
|
|
88
|
-
# Try fitting progressively fewer tags with +N suffix
|
|
89
87
|
(total - 1).downto(1) do |count|
|
|
90
88
|
remaining = total - count
|
|
91
89
|
suffix = " +#{remaining}"
|
|
@@ -95,17 +93,13 @@ module Railbow
|
|
|
95
93
|
end
|
|
96
94
|
end
|
|
97
95
|
|
|
98
|
-
#
|
|
99
|
-
# Need at least 7 chars: "● t… +N" (dot + color prefix use no visible width beyond the dot)
|
|
96
|
+
# A truncated first tag needs 7 columns at least: "● t… +N"
|
|
100
97
|
if max_width >= 7
|
|
101
98
|
suffix = (total > 1) ? " +#{total - 1}" : ""
|
|
102
|
-
# Available width for "● name…" part
|
|
103
99
|
avail = max_width - display_width(suffix)
|
|
104
|
-
# "● " prefix = 2 chars visible, plus at least 1 char of name + ellipsis (1 char)
|
|
105
100
|
if avail >= 4 # "● " (2) + at least 1 char + "…" (1)
|
|
106
101
|
first_plain = plain_segments[0] # e.g. "● some_table"
|
|
107
102
|
name_part = first_plain.sub(/^● /, "")
|
|
108
|
-
# Truncate name to fit: avail - 2 ("● ") - 1 ("…") = chars for name
|
|
109
103
|
name_max = avail - 2 - 1
|
|
110
104
|
truncated_name = name_part[0, name_max]
|
|
111
105
|
color_match = segments[0].match(/\e\[38;5;\d+m/)
|
|
@@ -114,16 +108,13 @@ module Railbow
|
|
|
114
108
|
end
|
|
115
109
|
end
|
|
116
110
|
|
|
117
|
-
# Absolute fallback: just +N
|
|
118
111
|
"+#{total}"
|
|
119
112
|
end
|
|
120
113
|
|
|
121
|
-
# Re-fits a Migration Name cell whose tags (branch, landed)
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
# stay put. Below NAME_TAGS_FLOOR there is no room for both and the tags
|
|
126
|
-
# are dropped outright - the name is what the row is about.
|
|
114
|
+
# Re-fits a Migration Name cell whose tags (branch, landed) sit flush
|
|
115
|
+
# right. Cutting from the right would eat the tags and leave a bare
|
|
116
|
+
# ellipsis, so the name gives up the space instead. Below NAME_TAGS_FLOOR
|
|
117
|
+
# the tags go: the name is what the row is about.
|
|
127
118
|
def name_with_tags_fitted(cell, max_width)
|
|
128
119
|
str = cell.to_s
|
|
129
120
|
return truncate_ansi(str, max_width) if display_width(strip_ansi(str)) <= max_width
|
|
@@ -170,16 +161,8 @@ module Railbow
|
|
|
170
161
|
|
|
171
162
|
def format_date(timestamp, mode = "full")
|
|
172
163
|
ts = timestamp.to_s
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
time = begin
|
|
176
|
-
Time.new(
|
|
177
|
-
ts[0..3].to_i, ts[4..5].to_i, ts[6..7].to_i,
|
|
178
|
-
ts[8..9].to_i, ts[10..11].to_i, ts[12..13].to_i
|
|
179
|
-
)
|
|
180
|
-
rescue ArgumentError
|
|
181
|
-
return ts
|
|
182
|
-
end
|
|
164
|
+
time = Railbow::Clock.from_version(ts)
|
|
165
|
+
return ts unless time
|
|
183
166
|
|
|
184
167
|
case mode
|
|
185
168
|
when "full"
|
|
@@ -195,8 +178,6 @@ module Railbow
|
|
|
195
178
|
end
|
|
196
179
|
end
|
|
197
180
|
|
|
198
|
-
private
|
|
199
|
-
|
|
200
181
|
def format_relative_time_from(time)
|
|
201
182
|
diff = Time.now - time
|
|
202
183
|
return "just now" if diff < 0
|
|
@@ -217,8 +198,6 @@ module Railbow
|
|
|
217
198
|
else "~#{years}y ago"
|
|
218
199
|
end
|
|
219
200
|
end
|
|
220
|
-
|
|
221
|
-
public
|
|
222
201
|
end
|
|
223
202
|
end
|
|
224
203
|
end
|
data/lib/railbow/init.rb
CHANGED
|
@@ -20,7 +20,7 @@ module Railbow
|
|
|
20
20
|
# Every key can also be overridden via RBW_* environment variables.
|
|
21
21
|
|
|
22
22
|
# Time window for migrations to display (e.g. 30d, 2mo, 1y, all)
|
|
23
|
-
since: "
|
|
23
|
+
since: "90d"
|
|
24
24
|
|
|
25
25
|
# Never show fewer than this many migrations, however old they are.
|
|
26
26
|
# The window above is a soft limit: a database with five migrations shows
|
|
@@ -60,6 +60,10 @@ module Railbow
|
|
|
60
60
|
# Date format: full, rel, short, or custom(%b %d, %Y)
|
|
61
61
|
# date: "full"
|
|
62
62
|
|
|
63
|
+
# Timezone dates are shown in: local (this machine), utc, or any zone
|
|
64
|
+
# Rails knows, e.g. America/New_York or "Eastern Time (US & Canada)"
|
|
65
|
+
# tz: "local"
|
|
66
|
+
|
|
63
67
|
# Multi-database runs (comma-separated). Leave empty ("") to expand every
|
|
64
68
|
# database into its own section, in database.yml order; a database with
|
|
65
69
|
# nothing in the time window collapses to a one-line summary either way.
|