railbow 0.3.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c42f0f6d8162662344a6ef8fb12388e8dd6a2a579384cab3829a85d806cb1571
4
- data.tar.gz: 064fb908f8cd7567e9d8be53800e6972aa48faf65348aff7338c6088aa7427dd
3
+ metadata.gz: d599ddd7c1bd3a12893312ab22f40ff75def87f5d85d42dd2ee5a11da7183225
4
+ data.tar.gz: 4413568aafb22c8cf2b1025bbd6f90696f4da6f3bcead307c48ffeac3e96f04c
5
5
  SHA512:
6
- metadata.gz: f7af392359a26db27919f6bac5d31ce22d6736615bbbe8d514c794e5114a2a71ff61081160340a3177fd4b172207b8c07ae2948fcfdaeaccbc0498b28370fb1c
7
- data.tar.gz: e5a9364c62c93fa530cbde3222c2249b3f251043514fda4d4cf51a33cfaf27ad54bffed5d5abae43befe065634faee0d96ec60581adc22004cd45790e7f87534
6
+ metadata.gz: a02d2dda50ba6fa9ae4b04d166ebc51148532dcfccd5ab1a7777179d14767d2bb87b2fc4a9a5d3aadbb0eeae114a68f5365cd3bfd0d27286958b192f5fa43967
7
+ data.tar.gz: 5ad80b381822e25a9078093f82c8b52deccae5940334ce45645883e2d4c06a81070e2710876f58fc8af949cc3b96009369e141eb9945c3a1941233561c31f486
data/CHANGELOG.md CHANGED
@@ -5,6 +5,101 @@ 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.5.0] - 2026-07-28
9
+
10
+ ### Added
11
+
12
+ - Multi-database support for `db:migrate:status`. Rails runs the task once per
13
+ database; railbow now wraps that loop and renders the databases together, as
14
+ one section each in `database.yml` order with column widths shared so the
15
+ tables line up. The section header names the database by its `database.yml`
16
+ name, which is what you configure and filter by, rather than the database
17
+ name Rails reports.
18
+ - A database with nothing in the time window and nothing pending collapses to a
19
+ one-line summary instead of an empty table, so the `cache`, `queue` and
20
+ `cable` databases of a stock Rails 8 app stop drowning out your own. Anything
21
+ it hides (pending migrations, ghosts) is counted in the line, along with the
22
+ flag that reveals them.
23
+ - Databases sharing a `migrations_paths` (horizontal sharding) merge into a
24
+ single table carrying a status glyph per database, making drift between
25
+ shards visible at a glance. A dim `·` marks a version one shard has never
26
+ seen.
27
+ - `RBW_DB` (config key `db`) selects and shapes a multi-database run:
28
+ `only:<name>` and `skip:<name>` (both repeatable) pick databases, `full`
29
+ draws the quiet ones in full, and `inline` merges everything into one table
30
+ ordered by version with a `Db` column, so the calendar spans the whole
31
+ application instead of restarting per database. Filtered-out databases are
32
+ named in a footer rather than silently dropped.
33
+
34
+ - `RBW_SINCE_MIN` (config key `since_min`, default 10) makes the time window a
35
+ soft limit: whatever `since` leaves, the floor tops it back up to this many
36
+ migrations. A database with five migrations now shows all five rather than
37
+ hiding the two that happen to be old and reporting an empty period. The
38
+ hidden-count line says `showing the last 10` when the floor overrode the
39
+ window, and a multi-database section header reads `last 10 of 50` rather than
40
+ `10 of 50`, so a table holding rows older than the window it names still
41
+ reads honestly in both views. `RBW_SINCE_MIN=0` restores the old hard cutoff.
42
+ - `RBW_DB=focus`, **the default**, expands only the first database and
43
+ summarizes the rest in a line each, so a multi-database app is one table to
44
+ read rather than several. A database holding pending migrations is expanded
45
+ anyway: the summary exists to hide what needs no action, and pending work is
46
+ the opposite of that. An empty `RBW_DB` (or `db: ""`) expands every database
47
+ that has recent activity, `full` expands everything including quiet ones, and
48
+ `focus` never applies to an `inline` run, which exists to hold every database
49
+ at once.
50
+
51
+ ### Changed
52
+
53
+ - Git lookups are now shared across a run: authors, landed dates and branch
54
+ origins are resolved once per migrations directory instead of once per
55
+ database. `RBW_HELP=1` prints the help once for the whole run rather than
56
+ once per database.
57
+ - `db:migrate:status` internals moved out of the rake file into
58
+ `Railbow::Status` (`Section`, `Printer`, `GitData`, `Ghosts`, `Help`). No
59
+ output changed: a single-database app renders byte for byte what it did
60
+ before, which a golden fixture now enforces.
61
+
62
+ ## [0.4.0] - 2026-07-28
63
+
64
+ ### Added
65
+
66
+ - `RBW_CALENDAR=wdividers` gives every ISO week its own separator row, the
67
+ fuller counterpart to `wticks`. A week that opens a new month is left to the
68
+ month row rather than being announced twice. The default stays `wticks`, so
69
+ existing output is unchanged until you opt in.
70
+ - `RBW_CALENDAR=counts` appends `· N migrations` to every separator row: how
71
+ many migrations that section holds, counted up to the next separator.
72
+ - `RBW_CALENDAR=wlabel:<fmt>` sets the strftime pattern for week rows,
73
+ alongside the existing `label:` for month rows. It defaults to the month
74
+ pattern, so the week number sits at the same offset on every separator row
75
+ rather than shifting left when no new month is announced.
76
+ - `railbow demo status` now honors `RBW_CALENDAR`, so the calendar modes can be
77
+ tried without a Rails project.
78
+ - An empty `RBW_CALENDAR` (month separators, no week markers) is now documented
79
+ in the help output, the `railbow init` template, and the README. It always
80
+ worked; nobody could have guessed it.
81
+
82
+ ### Changed
83
+
84
+ - Calendar separator rows are drawn in a muted purple (256-color 97) instead of
85
+ the brighter 141, so they frame the migrations without competing with them.
86
+ - Migrations that are not applied (`down`) now render as a greyed-out row in
87
+ `db:migrate:status`, instead of signalling their state through the status
88
+ column alone. The row keeps its layout and its status glyph keeps its color;
89
+ everything else drops its own colors, so pending migrations read as inactive
90
+ next to the applied ones.
91
+
92
+ ### Fixed
93
+
94
+ - Rows whose last column wrapped were indented with too wide a blank prefix:
95
+ building the row mutated the very cell that the wrapped-line indent is
96
+ measured from. Reachable from `routes`, `about`, and `stats`.
97
+ - A separator label wider than its column no longer pushes the rest of the row
98
+ sideways. Separator rows are drawn as empty walls with the label laid over
99
+ them, so a long label spills into the blank space to its right.
100
+ - `RBW_HELP` output for `db:migrate:status` uses plain hyphens instead of
101
+ em-dashes, matching the rest of the user-facing text.
102
+
8
103
  ## [0.3.0] - 2026-07-23
9
104
 
10
105
  ### Added
data/README.md CHANGED
@@ -21,7 +21,8 @@
21
21
  - **Notes** - `rails notes` with git blame, author colors, date filtering, and sorting
22
22
  - **About** - polished `rails about` output
23
23
  - **Git Integration** - authors, diffs, branch origin, landing dates, uncommitted file indicators
24
- - **Calendar View** - month separators and week tick markers for migration timelines
24
+ - **Calendar View** - month separators plus week ticks or full week rows, with optional per-section counts
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
25
26
  - **Ghost Recovery** - uses the [mighost](https://github.com/amberpixels/mighost) gem (optional) to recover names, authors, and branch badges for `NO FILE` migrations
26
27
  - **Smart Defaults** - auto-disables in CI, piped output, `NO_COLOR`, and LLM agents
27
28
 
@@ -103,8 +104,121 @@ Out of the box you get:
103
104
  - **Landing dates** - `⤻ Mar 04` badge when a migration was merged to main after its creation
104
105
  - **Branch badges** - `⎇ PS-142` showing the source branch/ticket
105
106
  - **Affected tables** - color-coded table names extracted from migration files
106
- - **Time filtering** - only the last 70 days shown by default (`since: 70d`)
107
+ - **Time filtering** - only the last 70 days shown by default (`since: 70d`), as a soft limit: never fewer than 10 migrations, however old (`since_min: 10`)
107
108
  - **Your migrations highlighted** - rows authored by you are visually distinct
109
+ - **Pending migrations greyed out** - a `down` row keeps its status glyph but loses its colors
110
+
111
+ #### Weekly view
112
+
113
+ By default each new ISO week is marked with a tick on the date column
114
+ (`calendar: wticks`). For a fuller weekly breakdown, `wdividers` gives every
115
+ week its own separator row, and `counts` appends how many migrations sit under
116
+ each separator, week or month:
117
+
118
+ ```bash
119
+ RBW_CALENDAR=wdividers,counts rake db:migrate:status
120
+ ```
121
+
122
+ ```
123
+ Feb 2026 W06 · 2 migrations
124
+ ↑↑ │ 20260202145343 │ 2026-02-02 14:53:43 │ Add weight field to animals ● animals
125
+ ↑↑ │ 20260203134528 │ 2026-02-03 13:45:28 │ Create vaccination records ● vaccination_records
126
+ Feb 2026 W07 · 1 migration
127
+ ↑↑ │ 20260210183902 │ 2026-02-10 18:39:02 │ Create pet tags ● pet_tags
128
+ Mar 2026 W10 · 1 migration
129
+ ↑↑ │ 20260303120000 │ 2026-03-03 12:00:00 │ Add breed restrictions to… ● adoption_policies
130
+ Mar 2026 W11 · 1 migration
131
+ ↑↑ │ 20260313132325 │ 2026-03-13 13:23:25 │ Create veterinary appointm… ⎇ PS-142 ● veterinary_appointments
132
+ ```
133
+
134
+ Every separator carries the month, so the week number always sits at the same
135
+ offset instead of shifting left on rows that open no new month, and every
136
+ separator is drawn in the same muted purple. Both labels are `strftime`
137
+ patterns: `label:` for months, `wlabel:` for weeks (defaulting to the same
138
+ pattern).
139
+
140
+ `RBW_CALENDAR` is the list of *week* markers, so an empty value leaves you with
141
+ month separators and nothing else:
142
+
143
+ ```yaml
144
+ view: "calendar,tables"
145
+ calendar: "" # month separators only
146
+ ```
147
+
148
+ Dropping `calendar` from `view` instead removes the month separators too.
149
+
150
+ #### Multiple databases
151
+
152
+ Rails runs `db:migrate:status` once per database, so a multi-database app used
153
+ to get one blind table per database: widths that did not line up, the help
154
+ printed once per database, and every git lookup repeated. Railbow now renders
155
+ the whole run together.
156
+
157
+ By default you read one table: the first database in `database.yml` is rendered
158
+ in full and every other one is summarized in a line each (`db: focus`).
159
+
160
+ ```
161
+ 📊 2 databases · primary, raw
162
+
163
+ ──── primary · myapp_development ──── 122 of 1500 ──────────────────────
164
+ ↑↑ │ 20260721130000 │ 2026-07-21 13:00:00 │ Partition sms notifications ● sms_notifications
165
+ ↓↓ │ 20260727170000 │ 2026-07-27 17:00:00 │ Add cr push columns to lines ● lines
166
+
167
+ ⋯ raw · 5 migrations, all applied · latest Jul 22 2026
168
+ ```
169
+
170
+ **A database with migrations pending is always expanded**, however focused the
171
+ run is. The summary exists to hide what needs no action, and pending work is
172
+ the opposite of that.
173
+
174
+ Setting `db` to an empty value expands every database that has recent activity,
175
+ each as its own section with column widths shared so the tables line up:
176
+
177
+ ```yaml
178
+ db: "" # or RBW_DB= on the command line
179
+ ```
180
+
181
+ ```
182
+ ──── primary · myapp_development ──── 122 of 1500 ──────────────────────
183
+ ↑↑ │ 20260721130000 │ 2026-07-21 13:00:00 │ Partition sms notifications ● sms_notifications
184
+
185
+ ──── raw · raw_development ─────────────────────────────────────────────
186
+ ↑↑ │ 20260722120000 │ 2026-07-22 12:00:00 │ Drop archived activity logs ● archived_activity_logs
187
+ ```
188
+
189
+ A section header counts what it is showing: `122 of 1500` means 122 fell inside
190
+ the time window. When the window found too few and the `since_min` floor topped
191
+ the section back up, it reads `last 10 of 50` instead, because the rows are
192
+ then the newest ten rather than a window's worth.
193
+
194
+ Even then, a database with nothing in the time window collapses to one line
195
+ rather than an empty table, which is what keeps the `cache`, `queue` and
196
+ `cable` databases of a stock Rails 8 app from drowning out your own:
197
+
198
+ ```
199
+ ⋯ cache · 5 migrations, all applied · latest Feb 12 2024
200
+ ⋯ queue · 12 migrations, 3 pending outside the 70d window - RBW_SINCE=all
201
+ ```
202
+
203
+ Databases that share a `migrations_paths` (horizontal sharding) run the same
204
+ files, so they merge into one table carrying a status per database. Drift
205
+ between shards is then visible at a glance, and `·` marks a version a shard has
206
+ never seen:
207
+
208
+ ```
209
+ ──── primary + primary_shard_one · db/migrate ──────────────────────────
210
+ ↑↑ ↑↑ │ 20260601151200 │ 2026-06-01 15:12:00 │ Add owner id to pets
211
+ ↑↑ ↓↓ │ 20260726143000 │ 2026-07-26 14:30:00 │ Add policy number to bills
212
+ ```
213
+
214
+ `RBW_DB` controls the rest: `only:<name>` and `skip:<name>` (both repeatable)
215
+ pick databases, `full` draws every section in full and overrides `focus`, and
216
+ `inline` merges everything into a single table ordered by version with a `Db`
217
+ column, so the calendar spans the whole application rather than restarting per
218
+ database. `focus` never applies to an inline run - a merged table exists to
219
+ hold every database at once.
220
+
221
+ A single-database app sees none of this: its output is unchanged.
108
222
 
109
223
  If the [mighost](https://github.com/amberpixels/mighost) gem is installed (`gem "mighost", group: [:development, :test]`), migrations whose files were deleted (e.g. after switching branches) show up with a 👻 status and their recovered name instead of a bare `********** NO FILE **********` row - Railbow talks to it through the stable `Mighost::API`. Each ghost row carries the most informative badge mighost can provide: `≡ <version>` when the migration was re-timestamped and lives on under another version (shown with a calmer 🪦 status), `⌥ <branch>` when a branch still holds the file, or `✂ deleted in:<sha>` pointing at the commit that removed it. Ghosts dismissed via `mighost:dismiss` (or hidden by `hide_superseded`) render as plain `NO FILE`.
110
224
 
@@ -153,10 +267,12 @@ rake railbow:init
153
267
 
154
268
  ```yaml
155
269
  since: 70d
270
+ since_min: 10
156
271
  date: rel
157
272
  git: "author:me,diff,mask:auto"
158
273
  view: "calendar,tables"
159
274
  calendar: wticks
275
+ db: focus
160
276
 
161
277
  compact: "maxw:120"
162
278
 
@@ -178,10 +294,12 @@ Every option can also be set via `RBW_*` environment variables, which override c
178
294
  | `RBW_PLAIN` | `1` | Disable all formatting |
179
295
  | `RBW_FORCE` | `1` | Force formatting even when piped, in CI, or run by an LLM agent (`RBW_PLAIN=1` still wins) |
180
296
  | `RBW_SINCE` | `2mo`, `70d`, `1y`, `all` | Filter migrations by time period |
297
+ | `RBW_SINCE_MIN` | `10`, `0` | Never show fewer than this many migrations, however old (default 10; `0` disables) |
181
298
  | `RBW_DATE` | `full`, `rel`, `short`, `custom(%b %d)` | Date display format |
182
299
  | `RBW_GIT` | `author:me,diff,mask:auto` | Git integration options |
183
300
  | `RBW_VIEW` | `calendar,tables` | Enable calendar view and table detection |
184
- | `RBW_CALENDAR` | `wticks` | Show week tick markers |
301
+ | `RBW_CALENDAR` | `wticks`, `wdividers,counts`, `` (empty) | Week markers: tick marks, full week separator rows, per-section counts. Empty means month separators only |
302
+ | `RBW_DB` | `focus` (default), `` (empty), `only:primary`, `skip:cache`, `full`, `inline` | Multi-database runs: focus one database, expand them all, pick some, or merge them into one table |
185
303
  | `RBW_COMPACT` | `oneline,dense,noheader,maxw:80` | Compact display options |
186
304
  | `RBW_VERB` | `GET,POST` | Filter routes by HTTP method |
187
305
  | `RBW_SORT` | `file`, `date` | Sort order for notes |
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Railbow
6
+ # Turns an ordered list of migration versions into the calendar furniture that
7
+ # gives the status table its shape: a separator row when the month changes,
8
+ # optionally one when the ISO week changes, and optionally a tick mark on the
9
+ # date column at each week boundary.
10
+ #
11
+ # A separator introduces the rows beneath it and runs until the next separator,
12
+ # so the optional count always answers "how many migrations in this section".
13
+ module Calendar
14
+ DEFAULT_MONTH_LABEL = "%b %Y W%V"
15
+ # Week rows carry the month too, so the week number sits at the same offset
16
+ # on every separator row rather than jumping left when the month is absent.
17
+ DEFAULT_WEEK_LABEL = DEFAULT_MONTH_LABEL
18
+
19
+ # What the renderer needs to draw the calendar: a label per separator row,
20
+ # and which rows open a week (for tick marks). Both keyed by row index.
21
+ class Furniture
22
+ attr_reader :separators, :tick_rows
23
+
24
+ def initialize
25
+ @separators = {}
26
+ @tick_rows = Set.new
27
+ end
28
+ end
29
+
30
+ module_function
31
+
32
+ def none
33
+ Furniture.new
34
+ end
35
+
36
+ def build(versions, weeks: false, ticks: false, counts: false,
37
+ month_label: DEFAULT_MONTH_LABEL, week_label: DEFAULT_WEEK_LABEL)
38
+ dates = versions.map { |v| parse_date(v) }
39
+ furniture = none
40
+
41
+ dates.each_with_index do |date, i|
42
+ prev = (i > 0) ? dates[i - 1] : nil
43
+ next unless date && prev
44
+
45
+ opens_week = week_key(prev) != week_key(date)
46
+
47
+ if month_key(prev) != month_key(date)
48
+ furniture.separators[i] = date.strftime(month_label)
49
+ elsif weeks && opens_week
50
+ furniture.separators[i] = date.strftime(week_label)
51
+ end
52
+
53
+ furniture.tick_rows << i if ticks && opens_week
54
+ end
55
+
56
+ append_counts(furniture.separators, dates.size) if counts
57
+ furniture
58
+ end
59
+
60
+ def parse_date(version)
61
+ v = version.to_s
62
+ Date.new(v[0..3].to_i, v[4..5].to_i, v[6..7].to_i)
63
+ rescue Date::Error
64
+ nil
65
+ end
66
+
67
+ def month_key(date)
68
+ [date.year, date.month]
69
+ end
70
+
71
+ # cwyear, not year: the ISO week of Jan 1 can belong to the year before.
72
+ def week_key(date)
73
+ [date.cwyear, date.cweek]
74
+ end
75
+
76
+ def append_counts(separators, total)
77
+ indices = separators.keys.sort
78
+ indices.each_with_index do |index, nth|
79
+ section_end = indices[nth + 1] || total
80
+ separators[index] = "#{separators[index]} · #{migration_count(section_end - index)}"
81
+ end
82
+ end
83
+
84
+ # Spelled out: a bare number next to a week label reads as part of the date.
85
+ def migration_count(count)
86
+ "#{count} #{(count == 1) ? "migration" : "migrations"}"
87
+ end
88
+ end
89
+ end
@@ -14,10 +14,12 @@ module Railbow
14
14
  }
15
15
  },
16
16
  "since" => "70d",
17
+ "since_min" => 10,
17
18
  "git" => "author:all,diff,mask:auto",
18
19
  "author_format" => "short",
19
20
  "view" => "calendar,tables",
20
- "calendar" => "wticks"
21
+ "calendar" => "wticks",
22
+ "db" => "focus"
21
23
  }.freeze
22
24
 
23
25
  @root = nil
@@ -3,7 +3,9 @@
3
3
  require "date"
4
4
  require_relative "../formatters/base"
5
5
  require_relative "../table"
6
+ require_relative "../calendar"
6
7
  require_relative "../config"
8
+ require_relative "../params"
7
9
  require_relative "fixtures"
8
10
 
9
11
  module Railbow
@@ -20,7 +22,8 @@ module Railbow
20
22
  puts "\n#{formatter.emoji(:status)} Database: #{formatter.cyan("db/development.sqlite3")}"
21
23
  puts
22
24
 
23
- # Use default config: author:me, diff, calendar, tables, wticks, full date
25
+ # Fixed for the showcase: author:me, diff, tables, full date. The calendar
26
+ # options below come from the real params, so RBW_CALENDAR still applies.
24
27
  date_format = "full"
25
28
  tables_enabled = true
26
29
  diff_enabled = true
@@ -28,7 +31,7 @@ module Railbow
28
31
  git_email = Fixtures::DEMO_USER_EMAIL
29
32
  git_name = Fixtures::DEMO_USER_NAME
30
33
 
31
- # Latest migration date for "fresh" landed badge detection
34
+ # Latest migration date - for "fresh" landed badge detection
32
35
  latest_version = migrations.last[:version]
33
36
  latest_mig_date = parse_version_date(latest_version)
34
37
 
@@ -43,7 +46,7 @@ module Railbow
43
46
  name_col_width = needs_name_truncation ? 60 : nil
44
47
 
45
48
  table_columns = [
46
- Railbow::Table::Column.new(label: "Status", max_width: 6, sticky: true),
49
+ Railbow::Table::Column.new(label: "Status", max_width: 6, sticky: true, accent: true),
47
50
  Railbow::Table::Column.new(label: "Migration ID", sticky: true),
48
51
  Railbow::Table::Column.new(label: "Created At"),
49
52
  Railbow::Table::Column.new(label: "Migration Name",
@@ -57,7 +60,9 @@ module Railbow
57
60
 
58
61
  # Build rows
59
62
  highlight_rows = Set.new
63
+ down_rows = Set.new
60
64
  rows = migrations.each_with_index.map do |m, idx|
65
+ down_rows << idx if m[:status] == "down"
61
66
  colored_status = case m[:status]
62
67
  when "up" then formatter.green_bold("up")
63
68
  when "down" then formatter.yellow_bold("down")
@@ -106,40 +111,16 @@ module Railbow
106
111
  [colored_status, m[:version], created_at, display_name, table_tags]
107
112
  end
108
113
 
109
- # Calendar separators
110
- separators = {}
111
- versions = migrations.map { |m| m[:version] }
112
- month_keys = versions.map { |v| v[0..5] }
113
- calendar_label_fmt = "%b %Y W%V"
114
-
115
- if month_keys.uniq.size > 1
116
- month_keys.each_with_index do |mk, i|
117
- next if i == 0
118
- if mk != month_keys[i - 1]
119
- v = versions[i]
120
- date = Date.new(v[0..3].to_i, v[4..5].to_i, v[6..7].to_i)
121
- separators[i] = date.strftime(calendar_label_fmt)
122
- end
123
- end
124
- end
125
-
126
- # Week ticks
127
- tick_rows = Set.new
128
- prev_week = nil
129
- versions.each_with_index do |v, i|
130
- y = v[0..3].to_i
131
- m = v[4..5].to_i
132
- d = v[6..7].to_i
133
- next if y == 0 || m == 0 || d == 0
134
-
135
- week = Date.new(y, m, d).cweek
136
-
137
- if i > 0 && prev_week && week != prev_week
138
- tick_rows << i
139
- end
140
-
141
- prev_week = week
142
- end
114
+ # Calendar furniture. Driven by the real params, so RBW_CALENDAR can be
115
+ # tried out here without a Rails project.
116
+ calendar = Railbow::Calendar.build(
117
+ migrations.map { |m| m[:version] },
118
+ weeks: Railbow::Params.calendar_wdividers?,
119
+ ticks: Railbow::Params.calendar_wticks?,
120
+ counts: Railbow::Params.calendar_counts?,
121
+ month_label: Railbow::Params.calendar_label,
122
+ week_label: Railbow::Params.calendar_week_label
123
+ )
143
124
 
144
125
  aliases = Railbow::Config.table_aliases
145
126
  renderer = Railbow::Table::Renderer.new(
@@ -148,7 +129,10 @@ module Railbow
148
129
  compact: {oneline: false, dense: false, noheader: false, maxw: nil, hidden_columns: []},
149
130
  aliases: aliases
150
131
  )
151
- puts renderer.render(rows, separators: separators, highlight_rows: highlight_rows, tick_rows: tick_rows, tick_col: 2)
132
+ puts renderer.render(rows,
133
+ separators: calendar.separators,
134
+ highlight_rows: highlight_rows, dim_rows: down_rows,
135
+ tick_rows: calendar.tick_rows, tick_col: 2)
152
136
  end
153
137
 
154
138
  private
data/lib/railbow/init.rb CHANGED
@@ -22,6 +22,11 @@ module Railbow
22
22
  # Time window for migrations to display (e.g. 30d, 2mo, 1y, all)
23
23
  since: "70d"
24
24
 
25
+ # Never show fewer than this many migrations, however old they are.
26
+ # The window above is a soft limit: a database with five migrations shows
27
+ # all five rather than reporting an empty period. 0 disables the floor.
28
+ since_min: 10
29
+
25
30
  # Sort order: file (by filename/version) or date (by timestamp)
26
31
  # sort: "file"
27
32
 
@@ -43,12 +48,29 @@ module Railbow
43
48
  # View mode (comma-separated): calendar, tables
44
49
  view: "calendar,tables"
45
50
 
46
- # Calendar options: wticks (show week tick separators)
51
+ # Calendar options (comma-separated), all require view: calendar.
52
+ # Leave empty ("") for month separators only, with no week markers.
53
+ # wticks - week tick marks on the date column
54
+ # wdividers - a separator row per ISO week
55
+ # counts - append "· N migrations" to separator rows (per section)
56
+ # label:<fmt> - strftime for month separators (default: %b %Y W%V)
57
+ # wlabel:<fmt> - strftime for week separators (default: same as label)
47
58
  calendar: "wticks"
48
59
 
49
60
  # Date format: full, rel, short, or custom(%b %d, %Y)
50
61
  # date: "full"
51
62
 
63
+ # Multi-database runs (comma-separated). Leave empty ("") to expand every
64
+ # database into its own section, in database.yml order; a database with
65
+ # nothing in the time window collapses to a one-line summary either way.
66
+ # focus - expand only the first database, summarizing the rest.
67
+ # A database with migrations pending is always expanded
68
+ # only:<name> - render only this database (repeatable)
69
+ # skip:<name> - drop this database (repeatable)
70
+ # full - draw every database in full, overriding focus
71
+ # inline - one merged table ordered by version, with a Db column
72
+ db: "focus"
73
+
52
74
  # Compact mode (comma-separated):
53
75
  # oneline - one line per migration
54
76
  # dense - reduce padding
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railbow
4
+ # Rails runs db:migrate:status once per database, inside
5
+ # with_temporary_pool_for_each. Each call on its own cannot know whether more
6
+ # databases are coming, so railbow wraps that loop: sections collect into the
7
+ # open batch and the batch renders them together when the loop ends.
8
+ #
9
+ # A batch is inert unless something adds to it, so wrapping the other tasks
10
+ # that share the same Rails helper (db:migrate, db:prepare) costs one object.
11
+ module MultiDb
12
+ KEY = :railbow_multi_db_batch
13
+
14
+ class Batch
15
+ attr_reader :git_cache
16
+
17
+ def initialize
18
+ @sections = []
19
+ @skipped = []
20
+ @git_cache = {}
21
+ @help_shown = false
22
+ end
23
+
24
+ # A database excluded by RBW_DB. Recorded rather than dropped so the run
25
+ # can say what it left out.
26
+ def skip(name)
27
+ @skipped << name.to_s
28
+ end
29
+
30
+ # Databases sharing a migrations path see the same files, so they merge
31
+ # into one section that carries a status per database.
32
+ def add(section)
33
+ existing = @sections.find { |s| s.same_migrations?(section) }
34
+ if existing
35
+ existing.merge!(section)
36
+ else
37
+ @sections << section
38
+ end
39
+ end
40
+
41
+ def claim_help
42
+ return false if @help_shown
43
+
44
+ @help_shown = true
45
+ end
46
+
47
+ def empty?
48
+ @sections.empty? && @skipped.empty?
49
+ end
50
+
51
+ def flush
52
+ return if empty?
53
+
54
+ Railbow::Status::Printer.new(@sections, skipped: @skipped).print
55
+ @sections = []
56
+ @skipped = []
57
+ end
58
+ end
59
+
60
+ module_function
61
+
62
+ # Opens a batch for the duration of the block. The flush is in an ensure so
63
+ # that a database aborting mid-run (a missing schema_migrations table, say)
64
+ # still prints the sections collected before it.
65
+ def batch
66
+ previous = Thread.current[KEY]
67
+ batch = Batch.new
68
+ Thread.current[KEY] = batch
69
+ begin
70
+ yield
71
+ ensure
72
+ Thread.current[KEY] = previous
73
+ batch.flush
74
+ end
75
+ end
76
+
77
+ def current
78
+ Thread.current[KEY]
79
+ end
80
+
81
+ # True the first time it is asked within a run. Without a batch every call
82
+ # is its own run, so the answer is always true.
83
+ def claim_help
84
+ current ? current.claim_help : true
85
+ end
86
+ end
87
+ end