diecut 0.0.1
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/bin/diecut +10 -0
- data/gem_test_suite.rb +17 -0
- data/lib/diecut/caller-locations-polyfill.rb +18 -0
- data/lib/diecut/cli.rb +77 -0
- data/lib/diecut/configurable.rb +142 -0
- data/lib/diecut/context-handler.rb +77 -0
- data/lib/diecut/errors.rb +14 -0
- data/lib/diecut/linter.rb +177 -0
- data/lib/diecut/mediator.rb +79 -0
- data/lib/diecut/mill.rb +68 -0
- data/lib/diecut/mustache.rb +20 -0
- data/lib/diecut/plugin-description/context-default.rb +13 -0
- data/lib/diecut/plugin-description/option.rb +63 -0
- data/lib/diecut/plugin-description.rb +147 -0
- data/lib/diecut/plugin-loader.rb +189 -0
- data/lib/diecut/report.rb +136 -0
- data/lib/diecut/template-reducer.rb +88 -0
- data/lib/diecut/template-set.rb +108 -0
- data/lib/diecut/template.rb +58 -0
- data/lib/diecut/ui-applier.rb +71 -0
- data/lib/diecut/ui-config.rb +31 -0
- data/lib/diecut.rb +54 -0
- data/spec/cli_spec.rb +1 -0
- data/spec/configurable_spec.rb +20 -0
- data/spec/linter_spec.rb +132 -0
- data/spec/mill_spec.rb +52 -0
- data/spec/plugin_loader_spec.rb +109 -0
- data/spec/register_plugin_spec.rb +105 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/template-reducer_spec.rb +36 -0
- data/spec/template_set_spec.rb +22 -0
- data/spec/template_spec.rb +60 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0db4143a62b998da4c0a37dca8bd1c10dfdc083e
|
4
|
+
data.tar.gz: 3df81689d7dd8c921dfba82edaed9aeaa5a60773
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8c8ad0152418a96ffde7d7a262d24868ff18c1604bed78699c618726d80806792eb5c344f1946af855ec9f7bf0599be0100878c7d3deecd2ffdf7718d86466d2
|
7
|
+
data.tar.gz: 524f260e59f3ee1375f432f9880d79e4aa9976ed127fb35a0767b7a40768943fc97ae741ceecf9c66c5567b946f9385dd85335fdac060e2aba57ff7af6e6cbbb
|
data/bin/diecut
ADDED
data/gem_test_suite.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
puts Dir::pwd
|
2
|
+
require 'test/unit'
|
3
|
+
begin
|
4
|
+
require 'spec'
|
5
|
+
rescue LoadError
|
6
|
+
false
|
7
|
+
end
|
8
|
+
|
9
|
+
class RSpecTest < Test::Unit::TestCase
|
10
|
+
def test_that_rspec_is_available
|
11
|
+
assert_nothing_raised("\n\n * RSpec isn't available - please run: gem install rspec *\n\n"){ ::Spec }
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_that_specs_pass
|
15
|
+
assert(system(*%w{spec -f e -p **/*.rb spec}),"\n\n * Specs failed *\n\n")
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Diecut
|
2
|
+
module CallerLocationsPolyfill
|
3
|
+
unless Kernel.instance_method(:caller_locations)
|
4
|
+
# :nocov:
|
5
|
+
FakeLocation = Struct.new(:absolute_path, :lineno, :label)
|
6
|
+
LINE_RE = %r[(?<absolute_path>[^:]):(?<lineno>\d+):(?:in `(?<label>[^'])')?]
|
7
|
+
# covers exactly the use cases we need
|
8
|
+
def caller_locations(range, length=nil)
|
9
|
+
caller[range.begin+1..range.end+1].map do |line|
|
10
|
+
if m = LINE_RE.match(line)
|
11
|
+
FakeLocation.new(m.named_captures.values_at("absolute_path", "lineno", "label"))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
# :nocov:
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/diecut/cli.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'diecut'
|
3
|
+
require 'diecut/mill'
|
4
|
+
|
5
|
+
module Diecut
|
6
|
+
class KindCli < Thor
|
7
|
+
include Thor::Actions
|
8
|
+
|
9
|
+
desc "generate TARGET", "Generate code"
|
10
|
+
def generate(target_dir)
|
11
|
+
self.destination_root = target_dir
|
12
|
+
|
13
|
+
mill = Mill.new(self.class.kind)
|
14
|
+
mill.activate_plugins {|name| options["with-#{name}"] }
|
15
|
+
|
16
|
+
ui = mill.user_interface
|
17
|
+
options.delete_if{|_, value| value.nil?}
|
18
|
+
ui.from_hash(options)
|
19
|
+
|
20
|
+
mill.churn(ui) do |path, contents|
|
21
|
+
create_file(path, contents)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
method_option :all_on => false
|
26
|
+
desc "lint", "Check well-formed-ness of code generators"
|
27
|
+
def lint
|
28
|
+
require 'diecut/linter'
|
29
|
+
mill = Mill.new(self.class.kind)
|
30
|
+
if options["all_on"]
|
31
|
+
mill.activate_plugins{ true }
|
32
|
+
else
|
33
|
+
mill.activate_plugins {|name| options["with-#{name}"] }
|
34
|
+
end
|
35
|
+
|
36
|
+
puts Linter.new(mill).report
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class CommandLine < Thor
|
41
|
+
def self.build_kind_subcommand(plugin_kind)
|
42
|
+
mediator = Diecut.mediator(plugin_kind)
|
43
|
+
example_ui = mediator.build_example_ui
|
44
|
+
|
45
|
+
klass = Class.new(KindCli) do
|
46
|
+
class << self
|
47
|
+
def kind(value = nil)
|
48
|
+
if @kind.nil?
|
49
|
+
@kind = value
|
50
|
+
end
|
51
|
+
@kind
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
mediator.plugins.each do |plugin|
|
56
|
+
class_option "with-#{plugin.name}", :default => plugin.default_active?
|
57
|
+
end
|
58
|
+
|
59
|
+
example_ui.field_names.each do |field|
|
60
|
+
method_option(field, {:for => :generate, :desc => example_ui.description(field) || field,
|
61
|
+
:required => example_ui.required?(field), :default => example_ui.default_for(field)})
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
klass.kind(plugin_kind)
|
66
|
+
|
67
|
+
klass
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.add_kind(kind)
|
71
|
+
desc "#{kind}", "Commands related to templating for #{kind}"
|
72
|
+
kind_class = build_kind_subcommand(kind)
|
73
|
+
const_set(kind.sub(/\A./){|match| match.upcase }, kind_class)
|
74
|
+
subcommand kind, kind_class
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'calibrate'
|
2
|
+
module Diecut
|
3
|
+
class Configurable
|
4
|
+
include Calibrate::Configurable
|
5
|
+
module ClassMethods
|
6
|
+
attr_accessor :target_name
|
7
|
+
|
8
|
+
def build_subclass(name)
|
9
|
+
Class.new(self).tap{|cc| cc.target_name = name }
|
10
|
+
end
|
11
|
+
|
12
|
+
def classname
|
13
|
+
name || superclass.name
|
14
|
+
end
|
15
|
+
|
16
|
+
def deep_field_names
|
17
|
+
field_names.map do |name|
|
18
|
+
field_value = field_metadata(name).default_value
|
19
|
+
if field_value==self
|
20
|
+
return ["LOOPED"]
|
21
|
+
end
|
22
|
+
if field_value.is_a?(Class) and field_value < Diecut::Configurable
|
23
|
+
field_value.deep_field_names.map do |subname|
|
24
|
+
"#{name}.#{subname}"
|
25
|
+
end
|
26
|
+
else
|
27
|
+
name
|
28
|
+
end
|
29
|
+
end.flatten
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
return "#<#{classname}:#{target_name}:(#{deep_field_names.join(",")})>"
|
34
|
+
end
|
35
|
+
|
36
|
+
def absorb_context(from)
|
37
|
+
from.field_names.each do |name|
|
38
|
+
from_metadata = from.field_metadata(name)
|
39
|
+
from_value = from_metadata.default_value
|
40
|
+
into_metadata = field_metadata(name)
|
41
|
+
|
42
|
+
if into_metadata.nil?
|
43
|
+
if from_value.is_a?(Class) and from_value < Calibrate::Configurable
|
44
|
+
nested = build_subclass("#{target_name}.#{name}")
|
45
|
+
setting(name, nested)
|
46
|
+
nested.absorb_context(from_value)
|
47
|
+
else
|
48
|
+
if from_metadata.is?(:required)
|
49
|
+
setting(name)
|
50
|
+
else
|
51
|
+
setting(name, from_value)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
next
|
55
|
+
end
|
56
|
+
into_value = into_metadata.default_value
|
57
|
+
if into_value.is_a?(Class) and into_value < Calibrate::Configurable
|
58
|
+
if from_value.is_a?(Class) and from_value < Calibrate::Configurable
|
59
|
+
into_value.absorb_context(from_value)
|
60
|
+
else
|
61
|
+
raise "Field clash: #{name.inspect} is already a complex value, but a simple value in the absorbed configurable"
|
62
|
+
end
|
63
|
+
else
|
64
|
+
unless from_value.is_a?(Class) and from_value < Calibrate::Configurable
|
65
|
+
# Noop - maybe should compare the default values? - should always
|
66
|
+
# be nil right now...
|
67
|
+
else
|
68
|
+
raise "Field clash: #{name.inspect} is already a simple value, but a complex value on the absorbed configurable"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def walk_path(field_path)
|
75
|
+
first, *rest = *field_path
|
76
|
+
|
77
|
+
segment = PathSegment.new(self, first.to_sym)
|
78
|
+
if rest.empty?
|
79
|
+
[segment]
|
80
|
+
else
|
81
|
+
[segment] + segment.nested.walk_path(rest)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def build_setting(field, is_section = false)
|
86
|
+
nested = walk_path(field).last.klass
|
87
|
+
|
88
|
+
if is_section
|
89
|
+
nested.setting(field.last, build_subclass("#{target_name}.#{field.last}"))
|
90
|
+
else
|
91
|
+
nested.setting(field.last)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
extend ClassMethods
|
96
|
+
|
97
|
+
def walk_path(field_path)
|
98
|
+
first, *rest = *field_path
|
99
|
+
|
100
|
+
segment = InstanceSegment.new(self, first.to_sym)
|
101
|
+
if rest.empty?
|
102
|
+
[segment]
|
103
|
+
else
|
104
|
+
[segment] + segment.value.walk_path(rest)
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
class InstanceSegment < ::Struct.new(:instance, :name)
|
110
|
+
def metadata
|
111
|
+
@metadata ||= instance.class.field_metadata(name)
|
112
|
+
end
|
113
|
+
|
114
|
+
def value
|
115
|
+
metadata.value_on(instance)
|
116
|
+
end
|
117
|
+
|
118
|
+
def value=(value)
|
119
|
+
instance.__send__(metadata.writer_method, value)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class PathSegment < ::Struct.new(:klass, :name)
|
124
|
+
def metadata
|
125
|
+
@metadata ||= klass.field_metadata(name)
|
126
|
+
end
|
127
|
+
|
128
|
+
def nested
|
129
|
+
@nested ||=
|
130
|
+
begin
|
131
|
+
if metadata.nil?
|
132
|
+
nested = Configurable.build_subclass("#{klass.target_name}.#{name}")
|
133
|
+
klass.setting(name, nested)
|
134
|
+
nested
|
135
|
+
else
|
136
|
+
metadata.default_value
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'diecut/errors'
|
2
|
+
module Diecut
|
3
|
+
class ContextHandler
|
4
|
+
attr_accessor :context_class, :ui_class, :plugins
|
5
|
+
|
6
|
+
def apply_simple_defaults
|
7
|
+
plugins.each do |plugin|
|
8
|
+
plugin.context_defaults.each do |default|
|
9
|
+
next unless default.simple?
|
10
|
+
begin
|
11
|
+
apply_simple_default(default)
|
12
|
+
rescue Error
|
13
|
+
raise Error, "Plugin #{plugin.name.inspect} failed"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def apply_to_ui
|
20
|
+
plugins.each do |plugin|
|
21
|
+
plugin.options.each do |option|
|
22
|
+
apply_option_to_ui(option)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def backfill_options_to_context
|
28
|
+
plugins.each do |plugin|
|
29
|
+
plugin.options.each do |option|
|
30
|
+
backfill_to_context(option)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def backfill_to_context(option)
|
36
|
+
return unless option.has_context_path?
|
37
|
+
|
38
|
+
segment = context_class.walk_path(option.context_path).last
|
39
|
+
if option.has_default?
|
40
|
+
segment.klass.setting(segment.name, option.default_value)
|
41
|
+
else
|
42
|
+
segment.klass.setting(segment.name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def apply_simple_default(default)
|
47
|
+
target = context_class.walk_path(default.context_path).last
|
48
|
+
if target.metadata.nil?
|
49
|
+
raise UnusedDefault, "No template uses a value at #{default.context_path.inspect}"
|
50
|
+
else
|
51
|
+
target.metadata.default_value = default.value
|
52
|
+
target.metadata.is(:defaulting)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def apply_option_to_ui(option)
|
57
|
+
ui_class.options_hash[option.name] = option
|
58
|
+
|
59
|
+
if option.has_context_path?
|
60
|
+
context_metadata = context_class.walk_path(option.context_path).last.metadata
|
61
|
+
if option.has_default?
|
62
|
+
ui_class.setting(option.name, option.default_value)
|
63
|
+
elsif context_metadata.is?(:defaulting)
|
64
|
+
ui_class.setting(option.name, context_metadata.default_value)
|
65
|
+
else
|
66
|
+
ui_class.setting(option.name)
|
67
|
+
end
|
68
|
+
else
|
69
|
+
if option.has_default?
|
70
|
+
ui_class.setting(option.name, option.default_value)
|
71
|
+
else
|
72
|
+
ui_class.setting(option.name)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Diecut
|
2
|
+
class Error < RuntimeError;
|
3
|
+
def message
|
4
|
+
if cause.nil?
|
5
|
+
super
|
6
|
+
else
|
7
|
+
super + " because: #{cause.message}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
class UnusedDefault < Error; end
|
12
|
+
class OverriddenDefault < Error; end
|
13
|
+
class InvalidConfig < Error; end
|
14
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'diecut/report'
|
2
|
+
|
3
|
+
module Diecut
|
4
|
+
class Linter
|
5
|
+
def initialize(mill)
|
6
|
+
@mill = mill
|
7
|
+
end
|
8
|
+
attr_reader :mill
|
9
|
+
|
10
|
+
def report
|
11
|
+
@ui = mill.user_interface
|
12
|
+
|
13
|
+
formatter = ReportFormatter.new([
|
14
|
+
option_collision_report,
|
15
|
+
orphaned_fields,
|
16
|
+
overridden_context_defaults
|
17
|
+
])
|
18
|
+
|
19
|
+
formatter.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
# Needed:
|
23
|
+
# Overridden option defaults (without plugin dep)
|
24
|
+
# Option with default, context with default (w/o PD)
|
25
|
+
|
26
|
+
def unindent(text)
|
27
|
+
indent = text.scan(/(^[ \t]*)\S/).map{|cap| cap.first}.max_by(&:length)
|
28
|
+
text.gsub(%r{^#{indent}},'')
|
29
|
+
end
|
30
|
+
|
31
|
+
def each_plugin
|
32
|
+
mill.mediator.activated_plugins.each do |plugin|
|
33
|
+
yield plugin
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def each_default
|
38
|
+
each_plugin do |plugin|
|
39
|
+
plugin.context_defaults.each do |default|
|
40
|
+
yield default, plugin
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def each_option
|
46
|
+
each_plugin do |plugin|
|
47
|
+
plugin.options.each do |option|
|
48
|
+
yield option, plugin
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def overridden_context_defaults
|
54
|
+
Report.new("Overridden context defaults", ["Output field", "Default value", "Source plugin"]).tap do |report|
|
55
|
+
default_values = Hash.new{|h,k| h[k]=[]}
|
56
|
+
each_default do |default, plugin|
|
57
|
+
next unless default.simple?
|
58
|
+
|
59
|
+
default_values[default.context_path] << [default, plugin]
|
60
|
+
end
|
61
|
+
|
62
|
+
default_values.each do |key, set|
|
63
|
+
default_values[key] = set.find_all do |plugin|
|
64
|
+
!set.any?{|child|
|
65
|
+
next if child == plugin
|
66
|
+
Diecut.plugin_loader.strict_sequence?(plugin[1], child[1])
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
default_values.each_value do |set|
|
72
|
+
if set.length > 1
|
73
|
+
set.each do |default, plugin|
|
74
|
+
|
75
|
+
report.add(default.context_path.join("."), default.value, plugin.name)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
unless report.empty?
|
81
|
+
report.fail("Multiple plugins assign different values to be rendered")
|
82
|
+
report.advice = unindent(<<-EOA)
|
83
|
+
This is a problem because each plugin may be assuming it's default
|
84
|
+
value, and since there's no guarantee in which order the plugins are
|
85
|
+
loaded, the actual default value is difficult to predict. In general,
|
86
|
+
this kind of override behavior can be difficult to reason about.
|
87
|
+
|
88
|
+
Either the collision is accidental, in which case the default value
|
89
|
+
should be removed from one plugin or the other. If the override is
|
90
|
+
intentional, then the overriding plugin's gem should depend on the
|
91
|
+
overridden one's - since you are overriding the value intentionally,
|
92
|
+
it makes sense to ensure that the value is there to override. Diecut
|
93
|
+
will load plugins such that the dependant plugins are loaded later,
|
94
|
+
which solves the predictability problem.
|
95
|
+
EOA
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def option_collision_report
|
101
|
+
Report.new("Option collisions", ["Output target", "Option name", "Source plugin"]).tap do |report|
|
102
|
+
option_targets = Hash.new{|h,k| h[k]=[]}
|
103
|
+
each_option do |option, plugin|
|
104
|
+
next unless option.has_context_path?
|
105
|
+
option_targets[option.context_path] << [plugin, option]
|
106
|
+
end
|
107
|
+
option_targets.each_value do |set|
|
108
|
+
if set.length > 1
|
109
|
+
set.each do |plugin, option|
|
110
|
+
report.add(option.context_path.join("."), option.name, plugin.name)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
unless report.empty?
|
116
|
+
report.fail("Multiple options assign the same values to be rendered")
|
117
|
+
report.advice = unindent(<<-EOA)
|
118
|
+
This is problem because two options in the user interface both change
|
119
|
+
rendered values. If a user supplies both with different values, the
|
120
|
+
output isn't predictable (either one might take effect).
|
121
|
+
|
122
|
+
Most likely, this is a simple error: remove options from each group
|
123
|
+
that targets the same rendered value until only one remains. It may
|
124
|
+
also be that one option has a typo - that there's a rendering target
|
125
|
+
that's omitted.
|
126
|
+
EOA
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def orphaned_fields
|
132
|
+
Report.new("Template fields all have settings", ["Output field", "Source file"]).tap do |report|
|
133
|
+
context_class = mill.context_class
|
134
|
+
|
135
|
+
required_fields = {}
|
136
|
+
|
137
|
+
context_class.field_names.each do |field_name|
|
138
|
+
if context_class.field_metadata(field_name).is?(:required)
|
139
|
+
required_fields[field_name.to_s] = []
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
mill.templates.all_templates.each do |template|
|
144
|
+
template.reduced.leaf_fields.each do |field|
|
145
|
+
field = field.join(".")
|
146
|
+
if required_fields.has_key?(field)
|
147
|
+
required_fields[field] << template.path
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
each_option do |option, plugin|
|
153
|
+
next unless option.has_context_path?
|
154
|
+
field = option.context_path.join(".")
|
155
|
+
required_fields.delete(field)
|
156
|
+
end
|
157
|
+
|
158
|
+
required_fields.each do |name, targets|
|
159
|
+
targets.each do |target|
|
160
|
+
report.add(name, target)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
unless report.empty?
|
165
|
+
report.status = "WARN"
|
166
|
+
report.advice = unindent(<<-EOA)
|
167
|
+
These fields might not receive a value during generation, which will
|
168
|
+
raise an error at use time.
|
169
|
+
|
170
|
+
It's possible these fields are set in a resolve block in one of the
|
171
|
+
plugins - Diecut can't check for that yet.
|
172
|
+
EOA
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'diecut/errors'
|
2
|
+
require 'diecut/ui-config'
|
3
|
+
require 'diecut/ui-applier'
|
4
|
+
require 'diecut/context-handler'
|
5
|
+
|
6
|
+
module Diecut
|
7
|
+
class Mediator
|
8
|
+
def initialize
|
9
|
+
@plugins = []
|
10
|
+
@activated = {}
|
11
|
+
end
|
12
|
+
attr_reader :plugins
|
13
|
+
|
14
|
+
def add_plugin(plug)
|
15
|
+
@activated[plug.name] = plug.default_activated
|
16
|
+
@plugins << plug
|
17
|
+
end
|
18
|
+
|
19
|
+
def activated?(plug_name)
|
20
|
+
@activated[plug_name]
|
21
|
+
end
|
22
|
+
|
23
|
+
def activate(plug_name)
|
24
|
+
@activated[plug_name] = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def deactivate(plug_name)
|
28
|
+
@activated[plug_name] = false
|
29
|
+
end
|
30
|
+
|
31
|
+
def activated_plugins
|
32
|
+
@plugins.find_all do |plugin|
|
33
|
+
@activated[plugin.name]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Set up context default settings
|
38
|
+
# set up ui settings from context
|
39
|
+
#
|
40
|
+
# < User gets involved >
|
41
|
+
#
|
42
|
+
def build_example_ui
|
43
|
+
ui_class = Class.new(UIConfig)
|
44
|
+
|
45
|
+
handler = ContextHandler.new
|
46
|
+
handler.context_class = Class.new(Configurable)
|
47
|
+
handler.ui_class = ui_class
|
48
|
+
handler.plugins = @plugins
|
49
|
+
|
50
|
+
handler.backfill_options_to_context
|
51
|
+
handler.apply_to_ui
|
52
|
+
|
53
|
+
handler.ui_class
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_ui_class(context_class)
|
57
|
+
ui_class = Class.new(UIConfig)
|
58
|
+
|
59
|
+
handler = ContextHandler.new
|
60
|
+
handler.context_class = context_class
|
61
|
+
handler.ui_class = ui_class
|
62
|
+
handler.plugins = activated_plugins
|
63
|
+
|
64
|
+
handler.apply_simple_defaults
|
65
|
+
handler.apply_to_ui
|
66
|
+
|
67
|
+
handler.ui_class
|
68
|
+
end
|
69
|
+
|
70
|
+
def apply_user_input(ui, context_class)
|
71
|
+
applier = UIApplier.new
|
72
|
+
applier.plugins = activated_plugins
|
73
|
+
applier.ui = ui
|
74
|
+
applier.context = context_class.new
|
75
|
+
applier.apply
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/diecut/mill.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'valise'
|
2
|
+
require 'diecut'
|
3
|
+
require 'diecut/template-set'
|
4
|
+
|
5
|
+
module Diecut
|
6
|
+
class Mill
|
7
|
+
def initialize(kind)
|
8
|
+
@kind = kind
|
9
|
+
end
|
10
|
+
attr_reader :kind
|
11
|
+
attr_writer :valise, :mediator, :templates
|
12
|
+
|
13
|
+
def mediator
|
14
|
+
@mediator ||= Diecut.mediator(kind)
|
15
|
+
end
|
16
|
+
|
17
|
+
def templates
|
18
|
+
@templates ||= TemplateSet.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def activate_plugins
|
22
|
+
mediator.plugins.map(&:name).each do |name|
|
23
|
+
if yield(name)
|
24
|
+
mediator.activate(name)
|
25
|
+
else
|
26
|
+
mediator.deactivate(name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def valise
|
32
|
+
@valise ||= mediator.activated_plugins.map do |plugin|
|
33
|
+
stem = plugin.stem_for(kind)
|
34
|
+
Valise::Set.define do
|
35
|
+
ro stem.template_dir
|
36
|
+
end.stemmed(stem.stem)
|
37
|
+
end.reduce{|left, right| left + right}.sub_set(kind)
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_files
|
41
|
+
valise.filter('**', %i[extended dotmatch]).files do |file|
|
42
|
+
templates.add(file.rel_path.to_s, file.contents)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def context_class
|
47
|
+
templates.context_class
|
48
|
+
end
|
49
|
+
|
50
|
+
def ui_class
|
51
|
+
mediator.build_ui_class(context_class)
|
52
|
+
end
|
53
|
+
|
54
|
+
def user_interface
|
55
|
+
load_files
|
56
|
+
templates.prepare
|
57
|
+
|
58
|
+
ui_class.new
|
59
|
+
end
|
60
|
+
|
61
|
+
def churn(ui)
|
62
|
+
templates.context = mediator.apply_user_input(ui, templates.context_class)
|
63
|
+
templates.results do |path, contents|
|
64
|
+
yield(path, contents)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|