mayu-live 0.0.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/COPYING +661 -0
- data/README.md +598 -0
- data/exe/mayu +33 -0
- data/lib/mayu/app_metrics.rb +93 -0
- data/lib/mayu/banner.rb +12 -0
- data/lib/mayu/client/README.md +17 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js +1 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.br +0 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.map +1 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.map.br +0 -0
- data/lib/mayu/client/dist/custom-elements/mayu-alert-cd7ad2a4.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-alert-cd7ad2a4.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-disconnected-9f349f46.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-disconnected-9f349f46.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-exception-63df4e8c.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-exception-63df4e8c.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-ping-c498c2a6.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-ping-c498c2a6.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-progress-bar-eb3e1ac8.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-progress-bar-eb3e1ac8.js.map +1 -0
- data/lib/mayu/client/dist/entries.json +3 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js +1 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js.br +0 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js.map +1 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js.map.br +0 -0
- data/lib/mayu/client/package.json +39 -0
- data/lib/mayu/client/rollup.config.js +81 -0
- data/lib/mayu/client/src/DecompressionStream.ts +15 -0
- data/lib/mayu/client/src/DecompressionStreamPolyfill.ts +43 -0
- data/lib/mayu/client/src/MimeTypes.ts +4 -0
- data/lib/mayu/client/src/NodeTree.ts +445 -0
- data/lib/mayu/client/src/custom-elements/mayu-alert.html +137 -0
- data/lib/mayu/client/src/custom-elements/mayu-alert.ts +62 -0
- data/lib/mayu/client/src/custom-elements/mayu-disconnected.html +134 -0
- data/lib/mayu/client/src/custom-elements/mayu-disconnected.ts +51 -0
- data/lib/mayu/client/src/custom-elements/mayu-exception.html +79 -0
- data/lib/mayu/client/src/custom-elements/mayu-exception.ts +28 -0
- data/lib/mayu/client/src/custom-elements/mayu-log.html +70 -0
- data/lib/mayu/client/src/custom-elements/mayu-log.ts +42 -0
- data/lib/mayu/client/src/custom-elements/mayu-ping.html +36 -0
- data/lib/mayu/client/src/custom-elements/mayu-ping.ts +53 -0
- data/lib/mayu/client/src/custom-elements/mayu-progress-bar.html +44 -0
- data/lib/mayu/client/src/custom-elements/mayu-progress-bar.ts +40 -0
- data/lib/mayu/client/src/custom-elements/types.d.ts +4 -0
- data/lib/mayu/client/src/global.d.ts +26 -0
- data/lib/mayu/client/src/h.ts +27 -0
- data/lib/mayu/client/src/logger.ts +56 -0
- data/lib/mayu/client/src/main.ts +271 -0
- data/lib/mayu/client/src/serializeEvent.ts +90 -0
- data/lib/mayu/client/src/stream.ts +175 -0
- data/lib/mayu/client/src/types.ts +1 -0
- data/lib/mayu/client/src/utils.ts +71 -0
- data/lib/mayu/client/tsconfig.json +18 -0
- data/lib/mayu/colors.rb +34 -0
- data/lib/mayu/commands/base.rb +22 -0
- data/lib/mayu/commands/build.rb +82 -0
- data/lib/mayu/commands.rb +53 -0
- data/lib/mayu/component/base.rb +177 -0
- data/lib/mayu/component/handler_ref.rb +99 -0
- data/lib/mayu/component/helpers.rb +93 -0
- data/lib/mayu/component/interface.rb +18 -0
- data/lib/mayu/component/wrapper.rb +165 -0
- data/lib/mayu/component.rb +54 -0
- data/lib/mayu/configuration.rb +195 -0
- data/lib/mayu/disable_sorbet.rb +23 -0
- data/lib/mayu/environment.rb +151 -0
- data/lib/mayu/event_stream.rb +158 -0
- data/lib/mayu/fetch.rb +88 -0
- data/lib/mayu/html.rb +53 -0
- data/lib/mayu/html.yaml +767 -0
- data/lib/mayu/message_cipher.rb +172 -0
- data/lib/mayu/message_cipher.test.rb +16 -0
- data/lib/mayu/metrics/collector.rb +161 -0
- data/lib/mayu/metrics/exporter.rb +47 -0
- data/lib/mayu/metrics/reporter.rb +187 -0
- data/lib/mayu/metrics.rb +82 -0
- data/lib/mayu/ref_counter.rb +57 -0
- data/lib/mayu/resources/README.md +14 -0
- data/lib/mayu/resources/asset.rb +71 -0
- data/lib/mayu/resources/assets.rb +76 -0
- data/lib/mayu/resources/dependency_graph.rb +306 -0
- data/lib/mayu/resources/dot_exporter.rb +167 -0
- data/lib/mayu/resources/generators/base.rb +18 -0
- data/lib/mayu/resources/generators/copy_file.rb +26 -0
- data/lib/mayu/resources/generators/image.rb +106 -0
- data/lib/mayu/resources/generators/write_file.rb +39 -0
- data/lib/mayu/resources/hot_swap/file_watcher.rb +69 -0
- data/lib/mayu/resources/hot_swap.rb +46 -0
- data/lib/mayu/resources/mermaid_exporter.rb +210 -0
- data/lib/mayu/resources/registry.rb +190 -0
- data/lib/mayu/resources/resolver/base.rb +32 -0
- data/lib/mayu/resources/resolver/filesystem.rb +94 -0
- data/lib/mayu/resources/resolver/static.rb +27 -0
- data/lib/mayu/resources/resolver.rb +13 -0
- data/lib/mayu/resources/resource.rb +150 -0
- data/lib/mayu/resources/transformers/__test__/css/adjacent_selectors.in.css +3 -0
- data/lib/mayu/resources/transformers/__test__/css/adjacent_selectors.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/attributes.in.css +3 -0
- data/lib/mayu/resources/transformers/__test__/css/attributes.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/composes.in.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/composes.out.css +10 -0
- data/lib/mayu/resources/transformers/__test__/css/element_selectors.in.css +3 -0
- data/lib/mayu/resources/transformers/__test__/css/element_selectors.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/has.in.css +7 -0
- data/lib/mayu/resources/transformers/__test__/css/has.out.css +10 -0
- data/lib/mayu/resources/transformers/__test__/css/media_queries.in.css +8 -0
- data/lib/mayu/resources/transformers/__test__/css/media_queries.out.css +12 -0
- data/lib/mayu/resources/transformers/__test__/css/pseudo_classes.in.css +5 -0
- data/lib/mayu/resources/transformers/__test__/css/pseudo_classes.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/README.md +10 -0
- data/lib/mayu/resources/transformers/__test__/haml/case.haml +8 -0
- data/lib/mayu/resources/transformers/__test__/haml/case.rb +15 -0
- data/lib/mayu/resources/transformers/__test__/haml/class_names.haml +13 -0
- data/lib/mayu/resources/transformers/__test__/haml/class_names.rb +26 -0
- data/lib/mayu/resources/transformers/__test__/haml/comments.haml +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/comments.rb +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/css.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/css.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/dashes.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/dashes.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return.haml +4 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return.rb +9 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return2.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return2.rb +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/handlers.haml +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/handlers.rb +12 -0
- data/lib/mayu/resources/transformers/__test__/haml/if_else.haml +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/if_else.rb +12 -0
- data/lib/mayu/resources/transformers/__test__/haml/interpolation.haml +8 -0
- data/lib/mayu/resources/transformers/__test__/haml/interpolation.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/object_ref_as_key.haml +1 -0
- data/lib/mayu/resources/transformers/__test__/haml/object_ref_as_key.rb +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/props.haml +4 -0
- data/lib/mayu/resources/transformers/__test__/haml/props.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots.haml +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots.rb +9 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_dynamic.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_dynamic.rb +9 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_fallback.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_fallback.rb +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing.haml +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing.rb +14 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing2.haml +10 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing2.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing3.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing3.rb +10 -0
- data/lib/mayu/resources/transformers/css/rouge_lexer.rb +841 -0
- data/lib/mayu/resources/transformers/css.rb +100 -0
- data/lib/mayu/resources/transformers/css.test.rb +87 -0
- data/lib/mayu/resources/transformers/haml.rb +984 -0
- data/lib/mayu/resources/transformers/haml.test.rb +114 -0
- data/lib/mayu/resources/types/README.md +36 -0
- data/lib/mayu/resources/types/base.rb +35 -0
- data/lib/mayu/resources/types/component.rb +198 -0
- data/lib/mayu/resources/types/image.rb +169 -0
- data/lib/mayu/resources/types/javascript.rb +50 -0
- data/lib/mayu/resources/types/nil.rb +23 -0
- data/lib/mayu/resources/types/stylesheet.rb +119 -0
- data/lib/mayu/resources/types/svg.rb +69 -0
- data/lib/mayu/resources/types.rb +37 -0
- data/lib/mayu/routes.rb +170 -0
- data/lib/mayu/routing/builder.rb +108 -0
- data/lib/mayu/routing/matcher.rb +58 -0
- data/lib/mayu/routing/routes.rb +85 -0
- data/lib/mayu/routing.rb +17 -0
- data/lib/mayu/server/app.rb +494 -0
- data/lib/mayu/server/controller.rb +152 -0
- data/lib/mayu/server/errors.rb +110 -0
- data/lib/mayu/server/file_server.rb +140 -0
- data/lib/mayu/server.rb +63 -0
- data/lib/mayu/session.rb +358 -0
- data/lib/mayu/state/README.md +6 -0
- data/lib/mayu/state/action_creator.rb +191 -0
- data/lib/mayu/state/action_wrapper.rb +30 -0
- data/lib/mayu/state/loader.rb +220 -0
- data/lib/mayu/state/store.rb +82 -0
- data/lib/mayu/state.rb +8 -0
- data/lib/mayu/state.test.rb +97 -0
- data/lib/mayu/utils.rb +114 -0
- data/lib/mayu/vdom/children.rb +117 -0
- data/lib/mayu/vdom/component_marshaler.rb +53 -0
- data/lib/mayu/vdom/css_attributes.rb +131 -0
- data/lib/mayu/vdom/descriptor.rb +151 -0
- data/lib/mayu/vdom/descriptor.test.rb +26 -0
- data/lib/mayu/vdom/dom.rb +239 -0
- data/lib/mayu/vdom/h.rb +22 -0
- data/lib/mayu/vdom/id_generator.rb +55 -0
- data/lib/mayu/vdom/interfaces.rb +186 -0
- data/lib/mayu/vdom/marshalling.rb +78 -0
- data/lib/mayu/vdom/reconciliation.rb +205 -0
- data/lib/mayu/vdom/reconciliation.test.rb +56 -0
- data/lib/mayu/vdom/special_elements.rb +108 -0
- data/lib/mayu/vdom/update_context.rb +180 -0
- data/lib/mayu/vdom/vdom.perf.test.rb +146 -0
- data/lib/mayu/vdom/vnode.rb +266 -0
- data/lib/mayu/vdom/vtree.rb +672 -0
- data/lib/mayu/vdom/vtree.test.rb +68 -0
- data/lib/mayu/vdom.rb +8 -0
- data/lib/mayu/vdom.test.rb +73 -0
- data/lib/mayu/version.rb +6 -0
- data/lib/mayu.rb +8 -0
- data/mayu-live.gemspec +70 -0
- metadata +612 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mayu
|
4
|
+
class RefCounter
|
5
|
+
extend T::Sig
|
6
|
+
extend T::Generic
|
7
|
+
|
8
|
+
Elem = type_member
|
9
|
+
|
10
|
+
sig { void }
|
11
|
+
def initialize
|
12
|
+
@refs = T.let(Hash.new { |h, k| h[k] = 0 }, T::Hash[Elem, Integer])
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { params(key: Elem).returns(Integer) }
|
16
|
+
def count(key)
|
17
|
+
@refs.fetch(key, 0)
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { returns(T::Array[Elem]) }
|
21
|
+
def keys
|
22
|
+
@refs.sort_by { _2 }.map(&:first)
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { params(key: Elem).void }
|
26
|
+
def acquire!(key)
|
27
|
+
@refs[key] = @refs[key].to_i + 1
|
28
|
+
end
|
29
|
+
|
30
|
+
sig do
|
31
|
+
type_parameters(:R)
|
32
|
+
.params(key: Elem, block: T.proc.returns(T.type_parameter(:R)))
|
33
|
+
.returns(T.type_parameter(:R))
|
34
|
+
end
|
35
|
+
def acquire(key, &block)
|
36
|
+
acquire!(key)
|
37
|
+
|
38
|
+
begin
|
39
|
+
yield
|
40
|
+
ensure
|
41
|
+
release(key)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { params(key: Elem).void }
|
46
|
+
def release(key)
|
47
|
+
count = @refs.fetch(key, nil)
|
48
|
+
return unless count
|
49
|
+
|
50
|
+
if count > 1
|
51
|
+
@refs[key] = count - 1
|
52
|
+
else
|
53
|
+
@refs.delete(key)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Mayu::Resources
|
2
|
+
|
3
|
+
This module contains classes for loading resources.
|
4
|
+
|
5
|
+
A resource is any type of file that can be used in an app,
|
6
|
+
usually a component, stylesheet or image.
|
7
|
+
|
8
|
+
In development mode, it is possible to watch some directories
|
9
|
+
for changes and reload resources dynamically as they are updated.
|
10
|
+
|
11
|
+
For production, all the resources will be serialized using
|
12
|
+
[Marshal](https://docs.ruby-lang.org/en/master/Marshal.html),
|
13
|
+
and static fileswill be generated during build time, and then
|
14
|
+
loaded in runtime.
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
require_relative "generators/image"
|
5
|
+
require_relative "generators/copy_file"
|
6
|
+
require_relative "generators/write_file"
|
7
|
+
|
8
|
+
module Mayu
|
9
|
+
module Resources
|
10
|
+
class Asset
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
class Status < T::Enum
|
14
|
+
enums do
|
15
|
+
Pending = new
|
16
|
+
Processing = new
|
17
|
+
Done = new
|
18
|
+
Failed = new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { returns(String) }
|
23
|
+
attr_reader :filename
|
24
|
+
sig { returns(Status) }
|
25
|
+
attr_reader :status
|
26
|
+
sig { returns(Generators::Base) }
|
27
|
+
attr_reader :generator
|
28
|
+
|
29
|
+
sig { params(filename: String, generator: Generators::Base).void }
|
30
|
+
def initialize(filename, generator)
|
31
|
+
@filename = filename
|
32
|
+
@generator = generator
|
33
|
+
@status = T.let(Status::Pending, Status)
|
34
|
+
end
|
35
|
+
|
36
|
+
sig { returns(T::Boolean) }
|
37
|
+
def pending? = @status == Status::Pending
|
38
|
+
sig { returns(T::Boolean) }
|
39
|
+
def processing? = @status == Status::Processing
|
40
|
+
sig { returns(T::Boolean) }
|
41
|
+
def done? = @status == Status::Done
|
42
|
+
sig { returns(T::Boolean) }
|
43
|
+
def failed? = @status == Status::Failed
|
44
|
+
|
45
|
+
sig { params(asset_dir: String).returns(T::Boolean) }
|
46
|
+
def process(asset_dir)
|
47
|
+
return false unless pending?
|
48
|
+
@status = Status::Processing
|
49
|
+
@generator.process(File.join(asset_dir, filename))
|
50
|
+
rescue StandardError
|
51
|
+
@status = Status::Failed
|
52
|
+
raise
|
53
|
+
else
|
54
|
+
@status = Status::Done
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
MarshalFormat = T.type_alias { [String] }
|
59
|
+
|
60
|
+
sig { returns(MarshalFormat) }
|
61
|
+
def marshal_dump
|
62
|
+
[@filename]
|
63
|
+
end
|
64
|
+
|
65
|
+
sig { params(args: MarshalFormat).void }
|
66
|
+
def marshal_load(args)
|
67
|
+
@filename = args.first
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
require "async/variable"
|
5
|
+
require "async/queue"
|
6
|
+
require "async/semaphore"
|
7
|
+
|
8
|
+
module Mayu
|
9
|
+
module Resources
|
10
|
+
class Assets
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig { void }
|
14
|
+
def initialize
|
15
|
+
@queue = T.let(Async::Queue.new, Async::Queue)
|
16
|
+
@results = T.let({}, T::Hash[String, Async::Variable])
|
17
|
+
@assets = T.let({}, T::Hash[String, Asset])
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { params(filename: String).void }
|
21
|
+
def wait_for(filename)
|
22
|
+
(@results[filename] ||= Async::Variable.new).wait
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { params(asset: Asset).void }
|
26
|
+
def add(asset)
|
27
|
+
@assets[asset.filename] ||= asset
|
28
|
+
@queue.enqueue(asset)
|
29
|
+
end
|
30
|
+
|
31
|
+
sig do
|
32
|
+
params(
|
33
|
+
asset_dir: String,
|
34
|
+
concurrency: Integer,
|
35
|
+
forever: T::Boolean,
|
36
|
+
task: Async::Task
|
37
|
+
).returns(Async::Task)
|
38
|
+
end
|
39
|
+
def run(
|
40
|
+
asset_dir,
|
41
|
+
concurrency:,
|
42
|
+
forever: false,
|
43
|
+
task: Async::Task.current
|
44
|
+
)
|
45
|
+
task.async do
|
46
|
+
semaphore = Async::Semaphore.new(concurrency)
|
47
|
+
|
48
|
+
while forever || !@queue.empty?
|
49
|
+
process(@queue.dequeue, asset_dir, semaphore)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
sig do
|
57
|
+
params(
|
58
|
+
asset: Asset,
|
59
|
+
asset_dir: String,
|
60
|
+
semaphore: Async::Semaphore
|
61
|
+
).void
|
62
|
+
end
|
63
|
+
def process(asset, asset_dir, semaphore)
|
64
|
+
semaphore.async do
|
65
|
+
if asset.process(asset_dir)
|
66
|
+
var = (@results[asset.filename] ||= Async::Variable.new)
|
67
|
+
var.resolve unless var.resolved?
|
68
|
+
end
|
69
|
+
rescue => e
|
70
|
+
Console.logger.error(self, e)
|
71
|
+
raise
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,306 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
require "tsort"
|
5
|
+
require "set"
|
6
|
+
require "cgi"
|
7
|
+
require_relative "mermaid_exporter"
|
8
|
+
require_relative "dot_exporter"
|
9
|
+
|
10
|
+
module Mayu
|
11
|
+
module Resources
|
12
|
+
class DependencyGraph
|
13
|
+
extend T::Sig
|
14
|
+
# This is basically a reimplementation of this library:
|
15
|
+
# https://github.com/jriecken/dependency-graph
|
16
|
+
|
17
|
+
class Direction < T::Enum
|
18
|
+
enums do
|
19
|
+
Incoming = new(:incoming)
|
20
|
+
Outgoing = new(:outgoing)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Node
|
25
|
+
extend T::Sig
|
26
|
+
|
27
|
+
sig { returns(Resource) }
|
28
|
+
attr_reader :resource
|
29
|
+
sig { returns(T::Set[String]) }
|
30
|
+
attr_reader :incoming
|
31
|
+
sig { returns(T::Set[String]) }
|
32
|
+
attr_reader :outgoing
|
33
|
+
|
34
|
+
sig { params(resource: Resource).void }
|
35
|
+
def initialize(resource)
|
36
|
+
@resource = resource
|
37
|
+
@incoming = T.let(Set.new, T::Set[String])
|
38
|
+
@outgoing = T.let(Set.new, T::Set[String])
|
39
|
+
end
|
40
|
+
|
41
|
+
sig { params(id: String).void }
|
42
|
+
def delete(id)
|
43
|
+
@incoming.delete(id)
|
44
|
+
@outgoing.delete(id)
|
45
|
+
end
|
46
|
+
|
47
|
+
MarshalFormat =
|
48
|
+
T.type_alias { [Resource, T::Set[String], T::Set[String]] }
|
49
|
+
|
50
|
+
sig { returns(MarshalFormat) }
|
51
|
+
def marshal_dump
|
52
|
+
[@resource, @incoming, @outgoing]
|
53
|
+
end
|
54
|
+
|
55
|
+
sig { params(dumped: MarshalFormat).void }
|
56
|
+
def marshal_load(dumped)
|
57
|
+
@resource, @incoming, @outgoing = dumped
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
sig { void }
|
62
|
+
def initialize
|
63
|
+
@nodes = T.let({}, T::Hash[String, Node])
|
64
|
+
end
|
65
|
+
|
66
|
+
sig { returns(Integer) }
|
67
|
+
def size = @nodes.size
|
68
|
+
|
69
|
+
sig { params(id: String).returns(T::Boolean) }
|
70
|
+
def include?(id) = @nodes.include?(id)
|
71
|
+
|
72
|
+
sig { params(id: String, resource: Resource).returns(Resource) }
|
73
|
+
def add_node(id, resource)
|
74
|
+
(@nodes[id] ||= Node.new(resource)).resource
|
75
|
+
end
|
76
|
+
|
77
|
+
sig { params(id: String).void }
|
78
|
+
def delete_node(id)
|
79
|
+
return unless @nodes.include?(id)
|
80
|
+
@nodes.delete(id)
|
81
|
+
delete_connections(id)
|
82
|
+
end
|
83
|
+
|
84
|
+
sig { params(id: String).void }
|
85
|
+
def delete_connections(id)
|
86
|
+
@nodes.each { |node| node.delete(id) }
|
87
|
+
end
|
88
|
+
|
89
|
+
sig { params(id: String).returns(T.nilable(Resource)) }
|
90
|
+
def get_resource(id)
|
91
|
+
@nodes[id]&.resource
|
92
|
+
end
|
93
|
+
|
94
|
+
sig { params(id: String).returns(T::Boolean) }
|
95
|
+
def has_node?(id)
|
96
|
+
@nodes.include?(id)
|
97
|
+
end
|
98
|
+
|
99
|
+
sig { params(source_id: String, target_id: String).void }
|
100
|
+
def add_dependency(source_id, target_id)
|
101
|
+
with_source_and_target(source_id, target_id) do |source, target|
|
102
|
+
source.outgoing.add(target_id)
|
103
|
+
target.incoming.add(source_id)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
sig { params(source_id: String, target_id: String).void }
|
108
|
+
def remove_dependency(source_id, target_id)
|
109
|
+
with_source_and_target(source_id, target_id) do |source, target|
|
110
|
+
source.outgoing.delete(target_id)
|
111
|
+
source.incoming.delete(source_id)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
sig { params(id: String).returns(T::Array[String]) }
|
116
|
+
def direct_dependencies_of(id)
|
117
|
+
@nodes.fetch(id).outgoing.to_a
|
118
|
+
end
|
119
|
+
|
120
|
+
sig { params(id: String).returns(T::Array[String]) }
|
121
|
+
def direct_dependants_of(id)
|
122
|
+
@nodes.fetch(id).incoming.to_a
|
123
|
+
end
|
124
|
+
|
125
|
+
sig do
|
126
|
+
params(
|
127
|
+
id: String,
|
128
|
+
started_at: T.nilable(String),
|
129
|
+
only_leaves: T::Boolean,
|
130
|
+
block: T.nilable(T.proc.params(arg0: String).returns(T::Boolean))
|
131
|
+
).returns(T::Set[String])
|
132
|
+
end
|
133
|
+
def dependencies_of(id, started_at = nil, only_leaves: false, &block)
|
134
|
+
raise "Circular" if id == started_at
|
135
|
+
|
136
|
+
@nodes
|
137
|
+
.fetch(id)
|
138
|
+
.outgoing
|
139
|
+
.map do |dependency|
|
140
|
+
next nil unless yield dependency if block_given?
|
141
|
+
|
142
|
+
dependencies = dependencies_of(dependency, started_at || id)
|
143
|
+
|
144
|
+
if !only_leaves || dependencies.empty?
|
145
|
+
dependencies.add(dependency)
|
146
|
+
else
|
147
|
+
dependencies
|
148
|
+
end
|
149
|
+
end
|
150
|
+
.compact
|
151
|
+
.reduce(Set.new, &:merge)
|
152
|
+
end
|
153
|
+
|
154
|
+
sig do
|
155
|
+
params(
|
156
|
+
id: String,
|
157
|
+
started_at: T.nilable(String),
|
158
|
+
only_leaves: T::Boolean
|
159
|
+
).returns(T::Set[String])
|
160
|
+
end
|
161
|
+
def dependants_of(id, started_at = nil, only_leaves: false)
|
162
|
+
raise "Circular" if id == started_at
|
163
|
+
|
164
|
+
@nodes
|
165
|
+
.fetch(id)
|
166
|
+
.incoming
|
167
|
+
.map do |dependant|
|
168
|
+
dependants = dependants_of(dependant, started_at || id)
|
169
|
+
if !only_leaves || dependants.empty?
|
170
|
+
dependants.add(dependant)
|
171
|
+
else
|
172
|
+
dependants
|
173
|
+
end
|
174
|
+
end
|
175
|
+
.reduce(Set.new, &:merge)
|
176
|
+
end
|
177
|
+
|
178
|
+
sig { returns(T::Array[String]) }
|
179
|
+
def entry_nodes
|
180
|
+
@nodes.filter { _2.incoming.empty? }.keys
|
181
|
+
end
|
182
|
+
|
183
|
+
sig { params(only_leaves: T::Boolean).returns(T::Array[String]) }
|
184
|
+
def overall_order(only_leaves: true)
|
185
|
+
TSort.tsort(
|
186
|
+
->(&b) { @nodes.keys.each(&b) },
|
187
|
+
->(key, &b) { @nodes[key]&.outgoing&.each(&b) }
|
188
|
+
)
|
189
|
+
end
|
190
|
+
|
191
|
+
sig { returns(String) }
|
192
|
+
def to_dot
|
193
|
+
DotExporter.new(self).to_source
|
194
|
+
end
|
195
|
+
|
196
|
+
sig { returns(String) }
|
197
|
+
def to_mermaid_source
|
198
|
+
MermaidExporter.new(self).to_source
|
199
|
+
end
|
200
|
+
|
201
|
+
sig { returns(String) }
|
202
|
+
def to_mermaid_url
|
203
|
+
MermaidExporter.new(self).to_url
|
204
|
+
end
|
205
|
+
|
206
|
+
sig { returns(T::Array[String]) }
|
207
|
+
def paths
|
208
|
+
@nodes.keys
|
209
|
+
end
|
210
|
+
|
211
|
+
sig { params(block: T.proc.params(arg0: Resource).void).void }
|
212
|
+
def each_resource(&block)
|
213
|
+
@nodes.each_value { |node| yield node.resource }
|
214
|
+
end
|
215
|
+
|
216
|
+
MarshalFormat = T.type_alias { T::Hash[String, Node] }
|
217
|
+
|
218
|
+
sig { returns(MarshalFormat) }
|
219
|
+
def marshal_dump
|
220
|
+
@nodes
|
221
|
+
end
|
222
|
+
|
223
|
+
sig { params(nodes: MarshalFormat).void }
|
224
|
+
def marshal_load(nodes)
|
225
|
+
@nodes = nodes
|
226
|
+
end
|
227
|
+
|
228
|
+
sig do
|
229
|
+
params(
|
230
|
+
id: String,
|
231
|
+
direction: Direction,
|
232
|
+
visited: T::Set[String],
|
233
|
+
block: T.proc.params(arg0: String).void
|
234
|
+
).void
|
235
|
+
end
|
236
|
+
def dfs2(id, direction, visited: T::Set[String].new, &block)
|
237
|
+
if visited.include?(id)
|
238
|
+
return
|
239
|
+
else
|
240
|
+
visited.add(id)
|
241
|
+
end
|
242
|
+
|
243
|
+
@nodes
|
244
|
+
.fetch(id)
|
245
|
+
.send(direction.serialize)
|
246
|
+
.each { dfs2(_1, direction, visited:, &block) }
|
247
|
+
|
248
|
+
yield id
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
sig do
|
254
|
+
params(
|
255
|
+
source_id: String,
|
256
|
+
target_id: String,
|
257
|
+
block: T.proc.params(arg0: Node, arg1: Node).void
|
258
|
+
).void
|
259
|
+
end
|
260
|
+
def with_source_and_target(source_id, target_id, &block)
|
261
|
+
yield(fetch_node(:source, source_id), fetch_node(:target, target_id))
|
262
|
+
end
|
263
|
+
|
264
|
+
sig { params(type: Symbol, id: String).returns(Node) }
|
265
|
+
def fetch_node(type, id)
|
266
|
+
@nodes.fetch(id) do
|
267
|
+
raise ArgumentError,
|
268
|
+
"Could not find #{type} #{id.inspect} in #{@nodes.keys.inspect}"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
sig do
|
273
|
+
params(
|
274
|
+
node: Node,
|
275
|
+
direction: Direction,
|
276
|
+
block: T.proc.params(arg0: Node).void
|
277
|
+
).void
|
278
|
+
end
|
279
|
+
def dfs(node, direction, &block)
|
280
|
+
node
|
281
|
+
.send(direction.serialize)
|
282
|
+
.each { |id| dfs(@nodes.fetch(id), direction, &block) }
|
283
|
+
|
284
|
+
yield node
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# if __FILE__ == $0
|
291
|
+
# graph = Resources::DependencyGraph.new
|
292
|
+
#
|
293
|
+
# graph.add_node("a")
|
294
|
+
# graph.add_node("b")
|
295
|
+
# graph.add_node("c")
|
296
|
+
#
|
297
|
+
# p graph.size
|
298
|
+
#
|
299
|
+
# graph.add_dependency("a", "b")
|
300
|
+
# graph.add_dependency("b", "c")
|
301
|
+
# p graph.dependencies_of("a")
|
302
|
+
# p graph.dependencies_of("b")
|
303
|
+
# p graph.dependants_of("c")
|
304
|
+
# p graph.overall_order
|
305
|
+
# p graph.overall_order(only_leaves: true)
|
306
|
+
# end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
require "base64"
|
5
|
+
require "zlib"
|
6
|
+
require_relative "dependency_graph"
|
7
|
+
|
8
|
+
module Mayu
|
9
|
+
module Resources
|
10
|
+
class DotExporter
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig { params(graph: DependencyGraph).void }
|
14
|
+
def initialize(graph)
|
15
|
+
@graph = graph
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { returns(String) }
|
19
|
+
def to_source
|
20
|
+
StringIO.new.tap { write_source(_1) }.tap(&:rewind).read.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { params(out: StringIO).returns(StringIO) }
|
24
|
+
def write_source(out)
|
25
|
+
entries = @graph.overall_order(only_leaves: false)
|
26
|
+
tree = make_tree(entries.map { _1.split("/") })
|
27
|
+
|
28
|
+
out.puts <<~EOF
|
29
|
+
strict digraph "dependency-cruiser output" {
|
30
|
+
ordering="out" rankdir="LR" splines="ortho" overlap="false" nodesep="0.16" ranksep="0.5" fontname="Helvetica-bold" fontsize="9" style="rounded,bold,filled" fillcolor="#ffffff" compound="true"
|
31
|
+
node [shape="box" style="rounded, filled" height="0.2" color="black" fillcolor="#ffffcc" fontcolor="black" fontname="Helvetica" fontsize="9"]
|
32
|
+
edge [arrowhead="normal" arrowsize="0.6" penwidth="2.0" color="#00000033" fontname="Helvetica" fontsize="9"]
|
33
|
+
|
34
|
+
EOF
|
35
|
+
|
36
|
+
entries.each do |entry|
|
37
|
+
fillcolor = "#bbfeff"
|
38
|
+
out.puts " #{entry.inspect} [label=<#{File.dirname(entry)}<BR/><B>#{File.basename(entry)}</B>> tooltip=#{File.basename(entry).inspect} fillcolor=#{fillcolor.inspect}]"
|
39
|
+
|
40
|
+
@graph
|
41
|
+
.direct_dependencies_of(entry)
|
42
|
+
.each do |dep|
|
43
|
+
color = "#e5009b99"
|
44
|
+
out.puts " #{entry.inspect} -> #{dep.inspect} [color=#{color.inspect}]"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
out.puts "}"
|
49
|
+
out
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
sig { params(path: String).returns(T.nilable(String)) }
|
55
|
+
def filetype_class(path)
|
56
|
+
case File.extname(path)
|
57
|
+
when ".rb"
|
58
|
+
"Ruby"
|
59
|
+
when ".css"
|
60
|
+
"CSS"
|
61
|
+
when ".png"
|
62
|
+
"Image"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
sig { params(str: String).returns(String) }
|
67
|
+
def encode(str)
|
68
|
+
str.gsub("/", "__").gsub("[", "__").gsub("]", "__")
|
69
|
+
end
|
70
|
+
|
71
|
+
sig { params(str: String).returns(String) }
|
72
|
+
def escape(str)
|
73
|
+
str.gsub(/\W/) { |ch| ch.codepoints.map { |cp| "##{cp};" }.join }
|
74
|
+
end
|
75
|
+
|
76
|
+
sig { params(str: String).returns(String) }
|
77
|
+
def display_name(str)
|
78
|
+
case File.extname(str)
|
79
|
+
when ".rb"
|
80
|
+
"fa:fa-gem #{str} "
|
81
|
+
when ".css"
|
82
|
+
"fab:fa-css3 #{str} "
|
83
|
+
when ".png"
|
84
|
+
"fa:fa-image #{str} "
|
85
|
+
else
|
86
|
+
str
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
sig do
|
91
|
+
params(out: StringIO, node: T.untyped, path: T::Array[String]).void
|
92
|
+
end
|
93
|
+
def print_routes(out, node, path = [])
|
94
|
+
node.each do |key, value|
|
95
|
+
path2 = path + [key]
|
96
|
+
|
97
|
+
if value.is_a?(String)
|
98
|
+
if key == "page.rb"
|
99
|
+
pathstr = path.flatten.join("/").sub(%r{\A/?}, "/")
|
100
|
+
out.puts " ROUTE__#{encode(value)}[#{pathstr.inspect}]"
|
101
|
+
end
|
102
|
+
else
|
103
|
+
print_routes(out, value, path2)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
sig do
|
109
|
+
params(out: StringIO, node: T.untyped, path: T::Array[String]).void
|
110
|
+
end
|
111
|
+
def print_route_edges(out, node, path = [])
|
112
|
+
node.each do |key, value|
|
113
|
+
path2 = path + [key]
|
114
|
+
|
115
|
+
if value.is_a?(String)
|
116
|
+
if key == "page.rb"
|
117
|
+
pathstr = path.flatten.join("/").sub(%r{\A/?}, "/")
|
118
|
+
out.puts " ROUTE__#{encode(value)}-->#{encode(value)}"
|
119
|
+
end
|
120
|
+
else
|
121
|
+
print_route_edges(out, value, path2)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
sig do
|
127
|
+
params(out: StringIO, node: T.untyped, path: T::Array[String]).void
|
128
|
+
end
|
129
|
+
def print_subgraphs(out, node, path = [])
|
130
|
+
level = path.length
|
131
|
+
indent = " " * level.succ
|
132
|
+
|
133
|
+
node.each do |key, value|
|
134
|
+
path2 = path + [key]
|
135
|
+
|
136
|
+
if value.is_a?(String)
|
137
|
+
out.puts "#{indent}#{encode(value)}[#{display_name(key).inspect}]"
|
138
|
+
else
|
139
|
+
pathstr = path2.flatten.join("/").sub(%r{\A/?}, "/")
|
140
|
+
out.puts "#{indent}subgraph PATH#{encode(pathstr)}[#{pathstr.inspect}]"
|
141
|
+
print_subgraphs(out, value, path2)
|
142
|
+
out.puts "#{indent}end"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
sig do
|
148
|
+
params(entries: T::Array[T::Array[String]], level: Integer).returns(
|
149
|
+
T.untyped
|
150
|
+
)
|
151
|
+
end
|
152
|
+
def make_tree(entries, level = 0)
|
153
|
+
entries
|
154
|
+
.group_by { _1[level] }
|
155
|
+
.transform_values do |paths|
|
156
|
+
paths
|
157
|
+
.partition { _1.length.pred <= level.succ }
|
158
|
+
.then do |leaves, branches|
|
159
|
+
leaves.each_with_object(
|
160
|
+
make_tree(branches, level + 1)
|
161
|
+
) { |leaf, obj| obj[leaf.last] = leaf.join("/") }
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
module Mayu
|
5
|
+
module Resources
|
6
|
+
module Generators
|
7
|
+
class Base
|
8
|
+
extend T::Sig
|
9
|
+
extend T::Helpers
|
10
|
+
abstract!
|
11
|
+
|
12
|
+
sig { abstract.params(target_path: String).void }
|
13
|
+
def process(target_path)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|