fled 0.0.2 → 0.0.3

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.
@@ -0,0 +1,70 @@
1
+ module DTC
2
+ module Utils
3
+ module Text
4
+ # Helper class for writting lines of text,
5
+ # with some indentation processing.
6
+ #
7
+ # Blocks of text can be indented and/or
8
+ # captured.
9
+ #
10
+ # Output is written to an array in `lines`
11
+ # by `push_raw`. Other methods use `split_lines`
12
+ # and/or `indent_lines` to preprocess input.
13
+ #
14
+ # Get the result by
15
+ class LineWriter
16
+ def lines ; @lines || [] end
17
+ def to_s sep = "\n"
18
+ lines.join(sep)
19
+ end
20
+ def begin_capture
21
+ (@lines_stack ||= []) << @lines
22
+ @lines = []
23
+ if block_given?
24
+ yield
25
+ end_capture
26
+ end
27
+ end
28
+ def end_capture
29
+ result = @lines
30
+ @lines = @lines_stack.pop
31
+ result
32
+ end
33
+ def push_raw *raw_lines
34
+ @lines = lines + raw_lines.flatten
35
+ end
36
+ def push_indent *indent, &blk
37
+ (@indents ||= []) << indent
38
+ if block_given?
39
+ yield
40
+ pop_indent
41
+ end
42
+ end
43
+ def pop_indent
44
+ @indents.pop
45
+ end
46
+ def current_indent
47
+ @indents && @indents.last
48
+ end
49
+ def push *lines
50
+ if (indent = current_indent)
51
+ push_raw(indent_lines(split_lines(*lines), indent))
52
+ else
53
+ push_raw *lines
54
+ end
55
+ end
56
+ alias_method :<<, :push
57
+ protected
58
+ def split_lines *lines
59
+ lines.flatten
60
+ end
61
+ def indent_lines lines, indent
62
+ lines = split_lines(lines)
63
+ lines.map { |line|
64
+ indent.join("") + line
65
+ }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,23 @@
1
+
2
+ module DTC
3
+ module Utils
4
+ module Text
5
+ def self.lines str
6
+ str.split(/\r?\n/).to_a
7
+ end
8
+ # Remove common space-only indent to all non-empty lines
9
+ def self.lines_without_indent lines
10
+ lines = self.lines(lines) unless lines.is_a?(Array)
11
+ lines.shift if lines.first.empty?
12
+ min_spaces = lines.map { |l|
13
+ l == "" ? nil : (l =~ /^( +)/ ? $1.length : 0)
14
+ }.select{ |e| e }.min || 0
15
+ lines.map { |l| (min_spaces == 0 ? l : l[min_spaces..-1]) || "" }
16
+ end
17
+
18
+ autoload :HTML, 'dtc/utils/text/html'
19
+ autoload :LineWriter, 'dtc/utils/text/line_writer'
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,98 @@
1
+ module DTC
2
+ module Utils
3
+ module Visitor
4
+ # Utilities for visiting a DSL block
5
+ #
6
+ # Tolerates chained method calls and
7
+ # recursive blocks, as long as intermediary
8
+ # method calls have no arguments.
9
+ #
10
+ # eg: `some.calls.are.fine(true)`
11
+ # `some(true).arent`
12
+ #
13
+ # Enters/leaves for methods called with a
14
+ # block. `Visitor#enter`s return is ignored.
15
+ module DSL
16
+ # Utility class to keep track of the
17
+ # running prefix as the DSL is visited
18
+ class RecursiveDSLDelegate
19
+ def initialize visitor, prefix = nil
20
+ @visitor = visitor
21
+ @prefix = prefix
22
+ @pending_prefix = nil
23
+ @called = false
24
+ end
25
+ def prefix sym
26
+ @called = true
27
+ flush
28
+ @pending_prefix = self.class.new(@visitor, with_prefix(sym))
29
+ end
30
+ def flush
31
+ if @pending_prefix
32
+ @pending_prefix.add_unless_called
33
+ @pending_prefix = nil
34
+ end
35
+ end
36
+ def add sym, *args
37
+ @called = true
38
+ @visitor.add(with_prefix(sym), *args)
39
+ end
40
+ def enter sym, *args
41
+ flush
42
+ @called = true
43
+ @visitor.enter(with_prefix(sym), *args)
44
+ end
45
+ def leave
46
+ flush
47
+ @visitor.leave
48
+ end
49
+ protected
50
+ def with_prefix sym
51
+ @prefix ? (sym ? "#{@prefix}.#{sym}".to_sym : @prefix) : sym
52
+ end
53
+ def add_unless_called
54
+ flush
55
+ @visitor.add(@prefix) unless @called
56
+ end
57
+ end
58
+ # Blank slate object providing the `self` context
59
+ # in which DSL blocks are evaluated
60
+ class RecursiveDSLContextBlank
61
+ extend DTC::Utils::Meta
62
+ blank_class :instance_exec, :class
63
+ def initialize delegate, unprefixed = delegate
64
+ @delegate = delegate
65
+ @unprefixed = unprefixed
66
+ end
67
+ def method_missing(meth, *args, &block)
68
+ if block
69
+ @delegate.enter meth, *args
70
+ self.class.new(@unprefixed, @unprefixed).instance_exec(&block)
71
+ @unprefixed.flush
72
+ @delegate.leave
73
+ else
74
+ if args.empty?
75
+ return self.class.new(@delegate.prefix(meth), @unprefixed)
76
+ else
77
+ @delegate.add(meth, *args)
78
+ end
79
+ end
80
+ self
81
+ end
82
+ end
83
+ # Visit the DSL provided in `blk` using `visitor`
84
+ #
85
+ # Context and delegate classes may be subclassed.
86
+ def self.accept visitor, context_klass = RecursiveDSLContextBlank,
87
+ delegate_klass = RecursiveDSLDelegate,
88
+ &blk
89
+ visitor = visitor.new() if visitor.is_a?(Class)
90
+ builder = delegate_klass.new(visitor)
91
+ context_klass.new(builder).instance_exec(&blk)
92
+ builder.flush
93
+ visitor
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,87 @@
1
+ module DTC
2
+ module Utils
3
+ module Visitor
4
+ module Folder
5
+ # Forwards visits only when file/folder name
6
+ # match the suite of regexen.
7
+ class FilenameFilteringVisitor < DTC::Utils::Visitor::FilteringForwarder
8
+ # `options` may define:
9
+ #
10
+ # - `:excluded`
11
+ # - `:excluded_files`
12
+ # - `:excluded_directories`
13
+ # - `:included`
14
+ # - `:included_files`
15
+ # - `:included_directories`
16
+ #
17
+ # Each item is then included only if no includes are defined,
18
+ # or it matches one of the includes, and if no exclusions are
19
+ # defined, or it does not match one of the exclusions.
20
+ #
21
+ # Each key of `options` is an array of strings that will be
22
+ # compiled into case-insensitive regexen
23
+ def initialize listener, options = {}
24
+ super listener
25
+ @excluded = compile_regexp(options[:excluded])
26
+ @excluded_files = compile_regexp(options[:excluded_files])
27
+ @excluded_directories = compile_regexp(options[:excluded_directories])
28
+ @included = compile_regexp(options[:included])
29
+ @included_files = compile_regexp(options[:included_files])
30
+ @included_directories = compile_regexp(options[:included_directories])
31
+ end
32
+ protected
33
+ def compile_regexp(rx_list)
34
+ return nil if rx_list.nil? || rx_list.reject { |e| e.length == 0 }.empty?
35
+ Regexp.union(*rx_list.map { |e| /#{e}/i })
36
+ end
37
+ def include?(is_file, name, *args)
38
+ name = File.basename(name) unless is_file
39
+ can_include = (@included.nil? || @included.match(name)) &&
40
+ ((is_file && (@included_files.nil? || @included_files.match(name))) ||
41
+ (!is_file && (@included_directories.nil? || @included_directories.match(name))))
42
+ if can_include
43
+ can_include = (@excluded.nil? || !@excluded.match(name)) &&
44
+ ((is_file && (@excluded_files.nil? || !@excluded_files.match(name))) ||
45
+ (!is_file && (@excluded_directories.nil? || !@excluded_directories.match(name))))
46
+ end
47
+ can_include
48
+ end
49
+ end
50
+ # Visit the path provided using `visitor`,
51
+ # (or a transparent FilenameFilteringVisitor if options
52
+ # include filtering)
53
+ #
54
+ # `options` may include a `:max_depth` key,
55
+ # or any keys used by `FilenameFilteringVisitor`
56
+ def self.accept visitor, path, options = {}
57
+ visitor = visitor.new() if visitor.is_a?(Class)
58
+ options = options.is_a?(Fixnum) ? ({:max_depth => options}) : options.dup
59
+ max_depth = options.delete(:max_depth) { -1 }
60
+ filter = options.empty? ? visitor :
61
+ FilenameFilteringVisitor.new(visitor, options)
62
+ accept_path filter, File.expand_path(path), max_depth
63
+ visitor
64
+ end
65
+ def self.accept_path visitor, path, max_depth = -1
66
+ dir = Dir.new(path)
67
+ return unless visitor.enter path
68
+ dir.each do |f|
69
+ full_path = File.join(path, f)
70
+ next if f == "." || f == ".."
71
+ if File.directory? full_path
72
+ return unless File.readable?(path)
73
+ if max_depth == 0
74
+ visitor.leave if visitor.enter(full_path)
75
+ else
76
+ self.accept_path(visitor, full_path, max_depth - 1)
77
+ end
78
+ else
79
+ visitor.add f, full_path
80
+ end
81
+ end
82
+ visitor.leave
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,272 @@
1
+ module DTC
2
+ module Utils
3
+ # Utilities for objects that repond to:
4
+ #
5
+ # - `enter(*arguments)`: return true to enter branch
6
+ # - `leave()`
7
+ # - `add(*arguments)
8
+ module Visitor
9
+ autoload :DSL, 'dtc/utils/visitor/dsl'
10
+ autoload :Folder, 'dtc/utils/visitor/folder'
11
+
12
+ # Forward visitor events to current
13
+ # value of `next_visitor`. Defaults
14
+ # to returning `true` for all `enter()`
15
+ class Forwarder
16
+ attr_accessor :next_visitor
17
+ def initialize next_visitor = nil
18
+ self.next_visitor = next_visitor if next_visitor
19
+ end
20
+ def enter *args
21
+ next_visitor ? next_visitor.enter(*args) : true
22
+ end
23
+ def add *args
24
+ next_visitor.add(*args) if next_visitor
25
+ end
26
+ def leave
27
+ next_visitor.leave if next_visitor
28
+ end
29
+ end
30
+
31
+ # Base class for visitors that redirect their calls
32
+ # to other visitors for sub-branches.
33
+ #
34
+ # Override `visitor_for_subtree` and return a new visitor
35
+ # to begin receiving calls *below* current symbol.
36
+ #
37
+ # When the branch is visited and after the new visitor
38
+ # is popped, `visitor_left_subtree` is called with original
39
+ # arguments as given to `enter`.
40
+ class Switcher < Forwarder
41
+ def initialize receiver
42
+ @visitor_full_stack = [receiver]
43
+ @visitor_stack = [[receiver]]
44
+ super receiver
45
+ end
46
+ def enter *args
47
+ sub_visitor = visitor_for_subtree(*args)
48
+ @visitor_full_stack.push(sub_visitor)
49
+ if sub_visitor
50
+ @visitor_stack.push([sub_visitor, *args])
51
+ self.next_visitor = sub_visitor
52
+ else
53
+ super
54
+ end
55
+ end
56
+ def leave
57
+ visitor = @visitor_full_stack.pop
58
+ if visitor
59
+ previous_visitor = @visitor_stack.pop
60
+ self.next_visitor = (@visitor_stack.last || []).first
61
+ visitor_left_subtree *previous_visitor
62
+ else
63
+ super
64
+ end
65
+ end
66
+ protected
67
+ def visitor_for_subtree *args
68
+ nil
69
+ end
70
+ def visitor_left_subtree visitor, *args
71
+ end
72
+ end
73
+
74
+ # Printing forwarding visitor
75
+ #
76
+ # The constructor accepts a block to replace the
77
+ # default printing mecanism.
78
+ class Printer < Forwarder
79
+ def initialize next_visitor = nil, &printer # :yields: depth, method, *args
80
+ @printer = printer || lambda { |depth, method, *args|
81
+ puts(
82
+ (" " * depth) +
83
+ method.inspect +
84
+ (args.empty? ? "" : " " + args.inspect)
85
+ )
86
+ }
87
+ @depth = 0
88
+ super next_visitor
89
+ end
90
+ def enter *args
91
+ print :enter, *args
92
+ @depth += 1
93
+ super
94
+ end
95
+ def leave
96
+ @depth -= 1
97
+ super
98
+ end
99
+ def add *args
100
+ print :add, *args
101
+ super
102
+ end
103
+ protected
104
+ def print method, *args
105
+ @printer.call(@depth, method, *args)
106
+ end
107
+ end
108
+
109
+ # Base class for visitors that create a data
110
+ # structure based on calls.
111
+ #
112
+ # Obtain result of visit with `root`
113
+ #
114
+ # Default behaviour is to build an
115
+ # array based structure. Override `new_inner`
116
+ # to create and return new inner nodes,
117
+ # and `new_outer` to create and return outer
118
+ # nodes.
119
+ #
120
+ # Example:
121
+ # DTC::Utils::Visitor::DSL::accept(DTC::Utils::Visitor::Builder) {
122
+ # container(:arg1) { child_item(:arg2) }
123
+ # root_child_item
124
+ # }.root
125
+ #
126
+ # =>
127
+ #
128
+ # [[:inner, [:container, :arg1], [:outer, [:child_item, :arg2]]],
129
+ # [:outer, [:root_child_item]]]
130
+ class Builder
131
+ def initialize root = []
132
+ @stack = [root]
133
+ end
134
+ def root
135
+ @stack.first
136
+ end
137
+ def enter *args
138
+ container = new_inner(*args)
139
+ @stack.push(container) if container
140
+ container
141
+ end
142
+ def leave
143
+ @stack.pop
144
+ end
145
+ def add *args
146
+ new_outer *args
147
+ end
148
+ protected
149
+ # Last value provided by `new_inner`, also
150
+ # known as "current parent"
151
+ def current_inner_node
152
+ @stack.last
153
+ end
154
+ def new_inner *args
155
+ container = [:inner, args]
156
+ current_inner_node << container
157
+ container
158
+ end
159
+ def new_outer *args
160
+ current_inner_node << [:outer, args]
161
+ end
162
+ end
163
+
164
+ # Adds a replay ability to the builder visitor.
165
+ class Recorder < Builder
166
+ # Replay all calls made on self to `visitor`
167
+ def accept visitor
168
+ visitor = visitor.new if visitor.is_a?(Class)
169
+ accept_inner visitor, root
170
+ end
171
+ protected
172
+ def accept_inner visitor, inner
173
+ inner.each do |node|
174
+ case node.first
175
+ when :outer
176
+ visitor.add *node.last
177
+ when :inner
178
+ if visitor.enter(*node[1])
179
+ accept_inner(visitor, node.drop(2))
180
+ visitor.leave
181
+ else
182
+ puts"NO"
183
+ end
184
+ else
185
+ raise RuntimeError, "Unknown node: #{node.inspect}"
186
+ end
187
+ end
188
+ visitor
189
+ end
190
+ end
191
+
192
+ # Subclasses the `Builder` to create
193
+ # hash based hierarchies, using the first
194
+ # argument as the key in the parent hash
195
+ #
196
+ # Does not tolerate duplicate keys by default.
197
+ # Override `key_collision` to change this.
198
+ #
199
+ # Example:
200
+ #
201
+ # DTC::Utils::Visitor::DSL::accept(DTC::Utils::Visitor::HashBuilder) {
202
+ # container(:arg1) { child_item(:arg2) }
203
+ # root_child_item
204
+ # }.root
205
+ #
206
+ # =>
207
+ #
208
+ # {:container=>{nil=>[:arg1], :child_item=>[:arg2]}, :root_child_item=>[]}
209
+ class HashBuilder < Builder
210
+ def initialize root = {}
211
+ super root
212
+ end
213
+ protected
214
+ def key_collision key, new_args, previous_args
215
+ raise RuntimeError, "Key #{key.inspect} already defined" if container[key]
216
+ end
217
+ def add_child key, value
218
+ container = current_inner_node
219
+ if (previous = container[key])
220
+ value = key_collision(key, value, previous)
221
+ end
222
+ container[key] = value
223
+ value
224
+ end
225
+ def new_inner key, *args
226
+ container = {}
227
+ container[nil] = args unless args.empty?
228
+ add_child key, container
229
+ end
230
+ def new_outer key, *args
231
+ add_child key, args
232
+ args
233
+ end
234
+ end
235
+
236
+ # Base class for forwarding visitors
237
+ # that forward events only for items
238
+ # on which a call to `include?(is_leaf, *args)`
239
+ # returns true.
240
+ #
241
+ # You can include, but not descend,
242
+ # a node by overriding `descend?(*args)` too.
243
+ class FilteringForwarder < Forwarder
244
+ def enter *args
245
+ return false unless include?(false, *args)
246
+ if (result = super) && !descend?(*args)
247
+ leave
248
+ return false
249
+ end
250
+ result
251
+ end
252
+ def add *args
253
+ return false unless include?(true, *args)
254
+ super
255
+ end
256
+ protected
257
+ def descend?(*args) ; true ; end
258
+ def include?(is_leaf, *args) ; true ; end
259
+ end
260
+
261
+ # Include this module in a class that
262
+ # responds to a flat tree visit, so only `add`
263
+ # methods, where the first argument is expected
264
+ # to be a symbol the class responds to
265
+ module AcceptAsFlatMethodCalls
266
+ def enter *args ; raise RuntimeError, "Blocks are not supported for #{self.class.name} (got on #{args.inspect})" ; end
267
+ def leave ; end
268
+ def add sym, *args ; self.__send__(sym, *args) ; end
269
+ end
270
+ end
271
+ end
272
+ end
data/lib/dtc/utils.rb CHANGED
@@ -2,8 +2,10 @@ module DTC
2
2
  module Utils
3
3
  autoload :Exec, 'dtc/utils/exec'
4
4
  autoload :InteractiveEditor, 'dtc/utils/interactive_edit'
5
+ autoload :Meta, 'dtc/utils/meta'
5
6
  autoload :MiniSelect, 'dtc/utils/mini_select'
6
- autoload :DSLDSL, 'dtc/utils/dsldsl'
7
7
  autoload :FileVisitor, 'dtc/utils/file_visitor'
8
+ autoload :Text, 'dtc/utils/text'
9
+ autoload :Visitor, 'dtc/utils/visitor'
8
10
  end
9
11
  end