Skip to content

Commit e86bbb7

Browse files
committed
Refresh rendered diagrams while watching circuit files
1 parent 3ca684d commit e86bbb7

3 files changed

Lines changed: 79 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ across `--step` images. The breadboard and netlist views accept v2 JSON IR.
218218
- `--diff OLD NEW` creates a two-panel HTML viewer. Removed wires are red in
219219
the old view and added wires are green in the new view; matching wires use
220220
their normal colors.
221+
- `--watch -o diagram.svg circuit.bk.rb` renders immediately and updates the
222+
file when a circuit or part file beside the input changes. Press Ctrl-C to stop.
221223
- `--print-template` makes a PDF with 2.54 mm hole spacing, the complete board,
222224
and the print palette. Print at 100% scale; the option fixes the output scale
223225
to 1 and requires `rsvg-convert`.
@@ -256,6 +258,7 @@ bkrender ../breadkit/examples/05_sensor_demo.bk.rb \
256258
| `--quality N` | `90` | JPEG and WebP quality. |
257259
| `--print-template` | off | Export a full-board PDF with physical hole spacing for 100% printing. |
258260
| `--static` | off | Omit SVG layer controls and scripts. |
261+
| `--watch` | off | Keep rendering to `-o PATH` when nearby circuit or part files change. |
259262
| `--force` | off | Draw resolved elements even when input has layout errors. |
260263
| `--render-timeout SECONDS` | 60 | Limit external raster conversion time. |
261264

‎lib/breadkit/render/cli.rb‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module Breadkit
77
module Render
88
class CLI
99
def run(argv)
10+
original_args = argv.dup
1011
options = { scale: 2.0, theme: "light", orientation: "portrait", color_by: "wire", crop: "auto", backend: "auto", quality: 90,
1112
view: "breadboard", label_density: "full" }
1213
parser = OptionParser.new do |opts|
@@ -33,6 +34,7 @@ def run(argv)
3334
opts.on("--backend NAME", %w[auto rsvg vips magick]) { |value| options[:backend] = value }
3435
opts.on("--background COLOR") { |value| options[:background] = value }
3536
opts.on("--static") { options[:static] = true }
37+
opts.on("--watch") { options[:watch] = true }
3638
opts.on("--quality N", Integer) { |value| options[:quality] = value }
3739
opts.on("--print-template") { options[:print_template] = true }
3840
opts.on("--render-timeout SECONDS", Float) { |value| options[:render_timeout] = value }
@@ -55,6 +57,11 @@ def run(argv)
5557
end
5658
raise ArgumentError, "--background is only supported for JPEG" if options[:background] && format != "jpeg"
5759
raise ArgumentError, "cannot write binary image data to a terminal; use -o PATH" if !%w[svg html].include?(format) && !options[:output] && $stdout.tty?
60+
if options[:watch]
61+
raise ArgumentError, "--watch requires -o PATH" unless options[:output]
62+
original_args.delete_at(original_args.index("--watch"))
63+
return watch(input, second_input, options[:annotations], options[:output], original_args)
64+
end
5865
return render_diff(input, second_input, options, format) if options[:diff]
5966
circuit = Breadkit.load(input)
6067
circuit.diagnostics.each do |item|
@@ -103,6 +110,35 @@ def run(argv)
103110

104111
private
105112

113+
def watch(input, second_input, annotations, output, args)
114+
root = File.dirname(File.expand_path(input))
115+
paths = [input, second_input, annotations].compact.map { |path| File.expand_path(path) }
116+
snapshot = watch_snapshot(root, paths, output)
117+
warn "bkrender: watching #{root}; press Ctrl-C to stop"
118+
loop do
119+
warn "bkrender: rendered #{output}" if run(args.dup).zero?
120+
loop do
121+
sleep 0.5
122+
updated = watch_snapshot(root, paths, output)
123+
next if updated == snapshot
124+
125+
snapshot = updated
126+
break
127+
end
128+
end
129+
end
130+
131+
def watch_snapshot(root, paths, output)
132+
# ponytail: watch nearby circuit and part files; add dependency tracking if broad projects make scanning costly.
133+
files = paths + Dir.glob(File.join(root, "**", "*.{bk.rb,yml,yaml,toml,json}"))
134+
files.map { |path| File.expand_path(path) }.uniq.sort.reject { |path| path == File.expand_path(output) }.to_h do |path|
135+
stat = File.stat(path)
136+
[path, [stat.mtime.to_r, stat.size]]
137+
rescue Errno::ENOENT
138+
[path, nil]
139+
end
140+
end
141+
106142
def validate_step_options(options, format)
107143
raise ArgumentError, "--step must be a positive integer" unless options[:step].positive?
108144
raise ArgumentError, "--step is unavailable in #{options[:view]} view" unless options[:view] == "breadboard"

‎spec/breadkit/render_spec.rb‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,46 @@
598598
end
599599

600600
RSpec.describe Breadkit::Render::CLI do
601+
it "requires file output for watch and notices source changes" do
602+
Dir.mktmpdir do |directory|
603+
input = File.join(directory, "circuit.bk.rb")
604+
included = File.join(directory, "parts.yml")
605+
output = File.join(directory, "circuit.svg")
606+
File.write(input, "board :mini\n")
607+
File.write(included, "id: first\n")
608+
cli = described_class.new
609+
expect { expect(cli.run([input, "--watch"])).to eq(2) }.to output(/--watch requires -o PATH/).to_stderr
610+
before = cli.send(:watch_snapshot, directory, [input], output)
611+
File.write(included, "id: second\n")
612+
after = cli.send(:watch_snapshot, directory, [input], output)
613+
expect(after).not_to eq(before)
614+
File.write(output, "generated")
615+
expect(cli.send(:watch_snapshot, directory, [input], output)).to eq(after)
616+
end
617+
end
618+
619+
it "rerenders a watched file after it changes" do
620+
Dir.mktmpdir do |directory|
621+
input = File.join(directory, "circuit.bk.rb")
622+
output = File.join(directory, "circuit.svg")
623+
File.write(input, 'board :mini; resistor :R1, "330", pins: %w[a1 a3]')
624+
ticks = 0
625+
cli = described_class.new
626+
allow(cli).to receive(:sleep) do
627+
ticks += 1
628+
if ticks == 1
629+
File.write(input, 'board :mini; resistor :R2, "330", pins: %w[a1 a3]')
630+
else
631+
raise Interrupt
632+
end
633+
end
634+
635+
expect { cli.run([input, "--watch", "-o", output]) }.to raise_error(Interrupt)
636+
expect(ticks).to eq(2)
637+
expect(File.read(output)).to include('data-ref="R2"')
638+
end
639+
end
640+
601641
it "passes label density to breadboard output and rejects it in netlist view" do
602642
Dir.mktmpdir do |directory|
603643
input = File.join(directory, "labels.bk.rb")

0 commit comments

Comments
 (0)