raindown 0.7.0 → 0.8.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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nodes/endpoint_node.rb +3 -2
  3. data/lib/raindown/actions.rb +5 -3
  4. data/lib/raindown/bare_directives.rb +14 -27
  5. data/lib/raindown/blueprints.rb +255 -0
  6. data/lib/raindown/cli.rb +39 -54
  7. data/lib/raindown/config.rb +2 -3
  8. data/lib/raindown/directives.rb +30 -13
  9. data/lib/raindown/document.rb +21 -15
  10. data/lib/raindown/endpoint/endpoint_runner.rb +13 -5
  11. data/lib/raindown/node_blocks.rb +22 -16
  12. data/lib/raindown/node_injector.rb +7 -10
  13. data/lib/raindown/pages.rb +1 -1
  14. data/lib/raindown/pre_renderer.rb +18 -0
  15. data/lib/raindown/tui/blueprints/blueprints_renderer.rb +93 -0
  16. data/lib/raindown/tui/blueprints/blueprints_view.rb +146 -0
  17. data/lib/raindown/tui/blueprints/canvas.rb +58 -0
  18. data/lib/raindown/tui/blueprints/code_comments.rb +71 -0
  19. data/lib/raindown/tui/blueprints/filter.rb +77 -0
  20. data/lib/raindown/tui/blueprints/traced_code.rb +56 -0
  21. data/lib/raindown/tui/blueprints/tracing.rb +129 -0
  22. data/lib/raindown/tui/{feelings/code_box.rb → code_box.rb} +44 -30
  23. data/lib/raindown/tui/endpoints.rb +2 -2
  24. data/lib/raindown/tui/events.rb +3 -1
  25. data/lib/raindown/tui/feelings/bins.rb +5 -5
  26. data/lib/raindown/tui/feelings/{celebration.rb → celebration/celebration.rb} +5 -2
  27. data/lib/raindown/tui/feelings/{scene.rb → celebration/scene.rb} +4 -1
  28. data/lib/raindown/tui/feelings/cell_moves.rb +34 -0
  29. data/lib/raindown/tui/feelings/feelings_code_box.rb +40 -0
  30. data/lib/raindown/tui/feelings/feelings_log.rb +22 -19
  31. data/lib/raindown/tui/feelings/feelings_renderer.rb +94 -95
  32. data/lib/raindown/tui/feelings/feelings_view.rb +28 -32
  33. data/lib/raindown/tui/feelings/magnifier.rb +68 -0
  34. data/lib/raindown/tui/feelings/motion.rb +93 -0
  35. data/lib/raindown/tui/feelings/waffle/baked_waffle.rb +88 -0
  36. data/lib/raindown/tui/feelings/waffle/balloons.rb +81 -0
  37. data/lib/raindown/tui/feelings/waffle/confetti.rb +55 -0
  38. data/lib/raindown/tui/feelings/waffle/sparkles.rb +61 -0
  39. data/lib/raindown/tui/feelings/waffle/waffle.rb +62 -0
  40. data/lib/raindown/tui/feelings/weekly_batch.rb +24 -5
  41. data/lib/raindown/tui/layout.rb +25 -18
  42. data/lib/raindown/tui/navigator/commands.rb +32 -1
  43. data/lib/raindown/tui/navigator/mouse.rb +43 -0
  44. data/lib/raindown/tui/navigator/navigator.rb +40 -44
  45. data/lib/raindown/tui/navigator/{search.rb → teleport.rb} +14 -12
  46. data/lib/raindown/tui/navigator/views.rb +2 -3
  47. data/lib/raindown/tui/{workers/project.rb → project.rb} +33 -22
  48. data/lib/raindown/tui/{menu_overlay.rb → project_box.rb} +1 -1
  49. data/lib/raindown/tui/renderers/renderer.rb +1 -0
  50. data/lib/raindown/tui/saves.rb +22 -0
  51. data/lib/raindown/tui/{search_overlay.rb → search_box.rb} +27 -14
  52. data/lib/raindown/tui/terminal/input.rb +58 -7
  53. data/lib/raindown/tui/terminal/screen.rb +31 -26
  54. data/lib/raindown/tui/terminal/styles.rb +55 -28
  55. data/lib/raindown/tui/terminal/terminal_title.rb +31 -0
  56. data/lib/raindown/tui/text.rb +35 -12
  57. data/lib/raindown/tui/views/dashboard_view.rb +2 -2
  58. data/lib/raindown/tui/views/endpoint_view.rb +3 -3
  59. data/lib/raindown/tui/views/list_view.rb +1 -1
  60. data/lib/raindown/tui/views/outline_view.rb +5 -4
  61. data/lib/raindown/tui.rb +17 -7
  62. data/lib/raindown/watcher/changes.rb +33 -0
  63. data/lib/raindown/watcher/file_watcher.rb +58 -0
  64. data/lib/raindown/watcher/project.rb +47 -0
  65. data/lib/raindown/watcher/supervisor.rb +111 -0
  66. data/lib/raindown/{tui/workers → workers}/worker.rb +37 -29
  67. data/lib/raindown/workers/workers.rb +63 -0
  68. data/lib/raindown.rb +4 -4
  69. data/lib/version.rb +1 -1
  70. metadata +37 -17
  71. data/lib/raindown/project_worker.rb +0 -48
  72. data/lib/raindown/supervisor.rb +0 -160
  73. data/lib/raindown/tui/feelings/waffle.rb +0 -43
  74. data/lib/raindown/tui/workers/workers.rb +0 -53
  75. data/lib/raindown/watcher.rb +0 -57
  76. /data/lib/raindown/tui/feelings/{fireworks.rb → celebration/fireworks.rb} +0 -0
  77. /data/lib/raindown/tui/feelings/{rain.rb → celebration/rain.rb} +0 -0
@@ -1,160 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'project_worker'
4
- require_relative 'projects'
5
-
6
- module Raindown
7
- # Watches every Raindown project in a directory, like ~/dev, and writes back to each Markdown file as it's saved.
8
- #
9
- # Each project's nodes load in a process of its own, forked once the shared code has loaded, so starting a project
10
- # costs almost nothing and two projects can have nodes of the same name. One listener watches the directory and sends
11
- # each changed file to its project's process. Without fork, as on Windows, the projects share this process.
12
- class Supervisor
13
- # Watching skips these folders as they hold dependencies and builds, which Projects ignores too.
14
- IGNORE = %r{(?:\A|/)(?:#{Projects::IGNORED.join('|')})/}
15
-
16
- attr_reader :directory, :workers
17
-
18
- def initialize(directory:, bare: false, execute: false)
19
- @directory = File.expand_path(directory)
20
- @bare = bare
21
- @execute = execute
22
- @workers = {}
23
- end
24
-
25
- def start
26
- require 'listen'
27
- $stdout.sync = true
28
- preload
29
-
30
- Projects.roots(directory).each { worker(it) }
31
- puts "Watching #{workers.size} #{workers.size == 1 ? 'project' : 'projects'} in #{directory} for changes..."
32
-
33
- trap_interrupt
34
- listener.start
35
- sleep
36
- ensure
37
- stop
38
- end
39
-
40
- # Sends each changed file to its project, starting a worker for a new project, or restarting one when its nodes change.
41
- def handle(paths)
42
- paths.each do |path|
43
- root = Projects.root(path) || next
44
-
45
- if Projects.node?(path, root:)
46
- restart(root)
47
- elsif Projects.markdown?(path, root:)
48
- worker(root).update(path)
49
- end
50
- end
51
- end
52
-
53
- def stop
54
- workers.each_value(&:stop)
55
- workers.clear
56
- end
57
-
58
- private
59
-
60
- def listener
61
- Listen.to(directory, ignore: IGNORE) { |modified, added, _removed| handle(modified + added) }
62
- end
63
-
64
- def worker(root)
65
- workers[root] ||= begin
66
- worker = ProjectWorker.new(root:, name: name(root), bare: @bare, execute: @execute)
67
- fork? ? ForkedWorker.new(worker).tap(&:start) : worker.tap(&:start)
68
- end
69
- end
70
-
71
- # Without fork the projects share this process, where nodes can't be unloaded.
72
- def restart(root)
73
- return warn("[#{name(root)}] Restart raindown to load the node changes") unless fork?
74
-
75
- worker = workers.delete(root) || return
76
- worker.stop
77
- puts "[#{worker.name}] Reloading nodes"
78
- worker(root)
79
- end
80
-
81
- # A project in the directory is named by its path from there, and the project the directory is in by its folder.
82
- def name(root)
83
- return File.basename(root) unless root.start_with?(File.join(directory, ''))
84
-
85
- root.delete_prefix(File.join(directory, ''))
86
- end
87
-
88
- # Loaded before forking, so every project's process shares them instead of loading its own.
89
- def preload
90
- return unless fork?
91
-
92
- require 'lownode'
93
- require 'dirload'
94
- end
95
-
96
- def fork?
97
- Process.respond_to?(:fork)
98
- end
99
-
100
- # Ctrl+C reaches every process in the terminal, so the workers stop with the watcher.
101
- def trap_interrupt
102
- trap('INT') { exit }
103
- end
104
- end
105
-
106
- # A project's worker in a forked process, which reads the files to update from a pipe until the pipe closes.
107
- # The process ends with exit! as it's forked mid-call, so exit would run the watcher's ensure and stop its siblings.
108
- class ForkedWorker
109
- def initialize(worker)
110
- @worker = worker
111
- end
112
-
113
- def name
114
- @worker.name
115
- end
116
-
117
- def start
118
- @writer&.close
119
- reader, @writer = IO.pipe
120
- @pid = fork do
121
- @writer.close
122
- trap('INT') { exit!(0) }
123
- @worker.start
124
- while (path = reader.gets)
125
- @worker.update(path.chomp)
126
- end
127
- exit!(0)
128
- end
129
- reader.close
130
- end
131
-
132
- # A worker that stopped, such as from a crash, starts again for the next file.
133
- def update(path, restarted: false)
134
- start unless alive?
135
- @writer.puts(path)
136
- @writer.flush
137
- rescue Errno::EPIPE
138
- raise if restarted
139
-
140
- start
141
- update(path, restarted: true)
142
- end
143
-
144
- def stop
145
- @writer&.close
146
- Process.kill('TERM', @pid) if alive?
147
- Process.wait(@pid)
148
- rescue Errno::ECHILD, Errno::ESRCH
149
- nil
150
- end
151
-
152
- private
153
-
154
- def alive?
155
- @pid && Process.waitpid(@pid, Process::WNOHANG).nil?
156
- rescue Errno::ECHILD
157
- false
158
- end
159
- end
160
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Raindown
4
- module TUI
5
- # The waffle after the rain with a pat of butter, when a batch of code is refined, and the week's work is done:
6
- # ╔═══╦═══╦═══╦═══╦═══╗
7
- # ║ ║ ║ ║ ║ ║
8
- # ╠═══╬═══╬═══╬═══╬═══╣
9
- # ║ ║ ║███║ ║ ║
10
- # ╠═══╬═══╬═══╬═══╬═══╣
11
- # ║ ║ ║ ║ ║ ║
12
- # ╚═══╩═══╩═══╩═══╩═══╝
13
- module Waffle
14
- EDGE = :code_number
15
- BUTTER = :button
16
- WAFFLE = [
17
- [['╔═══╦═══╦═══╦═══╦═══╗', EDGE]],
18
- [['║ ║ ║ ║ ║ ║', EDGE]],
19
- [['╠═══╬═══╬═══╬═══╬═══╣', EDGE]],
20
- [['║ ║ ║', EDGE], ['███', BUTTER], ['║ ║ ║', EDGE]],
21
- [['╠═══╬═══╬═══╬═══╬═══╣', EDGE]],
22
- [['║ ║ ║ ║ ║ ║', EDGE]],
23
- [['╚═══╩═══╩═══╩═══╩═══╝', EDGE]],
24
- [['100% refined', :feelings]],
25
- [['The work is mysterious and important.', :feelings]]
26
- ].freeze
27
-
28
- private
29
-
30
- def waffle
31
- top = [(height - WAFFLE.length) / 2, 0].max
32
- lines = [*Array.new(top, []), *WAFFLE, *Array.new(height, [])].first(height)
33
- lines.map { centred(it) }
34
- end
35
-
36
- # The parts in the middle of the width.
37
- def centred(parts)
38
- left = [(width - parts.sum { display_width(it[0]) }) / 2, 0].max
39
- padded([segment('', :blank, width: left), *parts.map { |text, style| segment(text, style, width: display_width(text)) }])
40
- end
41
- end
42
- end
43
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../../projects'
4
- require_relative 'worker'
5
-
6
- module Raindown
7
- module TUI
8
- # A Worker for each project in the terminal UI's folder: the folder's own, and every project inside it.
9
- # A file's requests go to the worker of the project it's in, the nearest one above it, even when it's new.
10
- # Workers start when they're first asked something.
11
- class Workers
12
- attr_reader :root, :roots
13
-
14
- def initialize(root:, fork: Worker.fork?)
15
- @root = root
16
- @fork = fork
17
- @workers = {}
18
- find_roots
19
- end
20
-
21
- def for(path)
22
- worker(Projects.root(path) || root)
23
- end
24
-
25
- # A worker for each project, as they were found when the workers last started.
26
- def each(&)
27
- roots.map { worker(it) }.each(&)
28
- end
29
-
30
- # Stops the workers, so the next requests start them again, loading their projects' nodes again,
31
- # and finds the projects again, like ones added since.
32
- def restart
33
- stop
34
- @workers.clear
35
- find_roots
36
- end
37
-
38
- def stop
39
- @workers.each_value(&:stop)
40
- end
41
-
42
- private
43
-
44
- def worker(project)
45
- @workers[project] ||= Worker.new(root: project, fork: @fork)
46
- end
47
-
48
- def find_roots
49
- @roots = [root, *Projects.roots(root)].uniq.sort
50
- end
51
- end
52
- end
53
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../raindown'
4
- require_relative 'rewriter'
5
-
6
- module Raindown
7
- # Watches one file and writes back to it as it's saved, like its TOC, tables and endpoint responses.
8
- class Watcher
9
- attr_reader :path, :bare, :execute
10
-
11
- def initialize(path:, bare: false, execute: false)
12
- @path = path
13
- @bare = bare
14
- @execute = execute
15
- end
16
-
17
- def start
18
- require 'listen'
19
-
20
- $stdout.sync = true
21
-
22
- safely { rewrite }
23
- puts "Watching #{path} for changes..."
24
-
25
- listener.start
26
- sleep
27
- end
28
-
29
- private
30
-
31
- def listener
32
- directory = File.dirname(File.expand_path(path))
33
- filename = File.basename(path)
34
-
35
- Listen.to(directory) do |modified, added, _removed|
36
- next unless (modified + added).any? { |f| File.basename(f) == filename }
37
-
38
- # Writing back saves the file again, and that save changes nothing, so it ends there.
39
- safely { puts "Updated #{path}" if rewrite }
40
- end
41
- end
42
-
43
- # An error in the file is shown rather than stopping the watcher, so it can be fixed and saved again.
44
- def safely
45
- yield
46
- rescue StandardError => e
47
- raise if ENV['RAINDOWN_DEBUG']
48
-
49
- warn "raindown: #{path}: #{e.message}"
50
- end
51
-
52
- # The files' metadata is loaded again each time, so new and renamed files are listed.
53
- def rewrite
54
- Rewriter.update(path:, bare:, execute:, metadata: Pages.for(path))
55
- end
56
- end
57
- end