fled 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.
@@ -0,0 +1,134 @@
1
+
2
+ require 'shellwords'
3
+ require 'open3'
4
+
5
+ module DTC
6
+ module Utils
7
+ # Execute a program with popen3, pipe data to-from a proc in an async loop
8
+ #
9
+ # i = 10
10
+ # Exec.run("cat",
11
+ # :input => "Initial input\n",
12
+ # :select_timeout => 1,
13
+ # :autoclose_stdin => false
14
+ # ) do |process, sout, serr, writer|
15
+ # if writer && (i -= 1) > 0 && i % 2 == 0
16
+ # puts "Writing"
17
+ # writer.call("Hello async world!\n", lambda { |*args| puts "Write complete" })
18
+ # elsif writer && i <= 0
19
+ # puts "Closing stdin"
20
+ # writer.call(nil, lambda { |*args| puts "Close complete" })
21
+ # end
22
+ # puts "Got #{sout.inspect}" if sout != ""
23
+ # end
24
+ class Exec
25
+ class << self
26
+ def sys *opts
27
+ system(opts.flatten.map {|e| Shellwords::shellescape(e.to_s)}.join(" "))
28
+ raise "External command error" unless $?.success?
29
+ end
30
+ def sys_in cwd, *opts
31
+ Dir.chdir cwd { sys(*opts) }
32
+ end
33
+ def rsys *opts
34
+ res = `#{opts.map {|e| Shellwords::shellescape(e.to_s)}.join(" ")}`
35
+ $?.success? ? res : nil
36
+ end
37
+ def rsys_in cwd, *opts
38
+ Dir.chdir cwd { rsys(*opts) }
39
+ end
40
+ def git *opts
41
+ sys(*(%w[git] + opts))
42
+ end
43
+ def git_in cwd, *opts
44
+ Dir.chdir(cwd) { git(*opts) }
45
+ end
46
+ def rgit *opts
47
+ rsys(*(%w[git] + opts))
48
+ end
49
+ def rgit_in cwd, *opts
50
+ Dir.chdir(cwd) { return rgit(*opts) }
51
+ end
52
+ end
53
+
54
+ def initialize *cmd
55
+ options = cmd.last.is_a?(Hash) ? cmd.pop() : {}
56
+ @input = options.delete(:input)
57
+ @autoclose_stdin = options.delete(:autoclose_stdin) { true }
58
+ @cmd = cmd + options.delete(:cmd) { [] }
59
+ @select_timeout = options.delete(:select_timeout) { 5 }
60
+ @running = false
61
+ @ran = false
62
+ end
63
+ attr_accessor :input
64
+ attr_accessor :cmd
65
+ attr_reader :ran, :running, :exec_time, :stdout, :stderr
66
+ def run # :yields: exec, new_stdout, new_stderr, write_proc
67
+ @start_time = Time.new
68
+ @stdout = ""
69
+ @stderr = ""
70
+ @running = true
71
+ stdin, stdout, stderr = Open3::popen3(*@cmd)
72
+ begin
73
+ stdout_read, stderr_read = "", ""
74
+ MiniSelect.run(@select_timeout) do |select|
75
+ writing = 0
76
+ write_proc = lambda do |*args|
77
+ text, callback = *args
78
+ if text.nil?
79
+ write_proc = nil
80
+ select.close(stdin) do |miniselect, event, file, error|
81
+ callback.call(miniselect, event, file, error) if callback
82
+ end
83
+ else
84
+ writing += 1
85
+ select.write(stdin, text) do |miniselect, event, file, error|
86
+ if event == :error || event == :close
87
+ writing = 0
88
+ write_proc = nil
89
+ else
90
+ writing -= 1
91
+ end
92
+ callback.call(miniselect, event, file, error) if callback
93
+ end
94
+ end
95
+ end
96
+ select.add_read(stdout) do |miniselect, event, file, data_string|
97
+ if data_string
98
+ @stdout << data_string
99
+ stdout_read << data_string
100
+ end
101
+ end
102
+ select.add_read(stderr) do |miniselect, event, file, data_string|
103
+ if data_string
104
+ @stderr << data_string
105
+ stderr_read << data_string
106
+ end
107
+ end
108
+ select.every_beat do |miniselect|
109
+ yield(self, stdout_read, stderr_read, write_proc) if block_given?
110
+ if write_proc && writing == 0 && @autoclose_stdin
111
+ select.close(stdin)
112
+ write_proc = nil
113
+ end
114
+ stdout_read, stderr_read = "", ""
115
+ end
116
+ write_proc.call(@input) if @input
117
+ end
118
+ if stdout_read != "" || stderr_read != ""
119
+ yield(self, stdout_read, stderr_read, nil) if block_given?
120
+ end
121
+ ensure
122
+ [stdin, stdout, stderr].each { |f| f.close() if f && !f.closed? }
123
+ @running = false
124
+ @ran = true
125
+ end
126
+ @exec_time = Time.new - @start_time
127
+ end
128
+ # <code>self.new(&block).run(*args)</code>
129
+ def self.run *args, &block
130
+ self.new(*args).run(&block)
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,78 @@
1
+ module DTC
2
+ module Utils
3
+ class FileVisitor
4
+ def depth ; @folders.nil? ? 0 : @folders.count ; end
5
+ def current_path *args ; File.join(@folders + args) ; end
6
+ attr_accessor :next_visitor
7
+ def enter_folder dir
8
+ return false unless next_visitor ? next_visitor.enter_folder(dir) : true
9
+ (@folders ||= []) << dir
10
+ true
11
+ end
12
+ def visit_file name, full_path
13
+ next_visitor.visit_file(name, full_path) if next_visitor
14
+ end
15
+ def leave_folder
16
+ next_visitor.leave_folder if next_visitor
17
+ @folders.pop
18
+ end
19
+ def self.browse path, visitor, max_depth = -1
20
+ dir = Dir.new(path)
21
+ return unless visitor.enter_folder path
22
+ dir.each do |f|
23
+ full_path = File.join(path, f)
24
+ next if f == "." || f == ".."
25
+ if File.directory? full_path
26
+ self.browse(full_path, visitor, max_depth - 1) unless max_depth == 0
27
+ else
28
+ visitor.visit_file f, full_path
29
+ end
30
+ end
31
+ visitor.leave_folder
32
+ end
33
+ end
34
+ class FilteringFileVisitor < FileVisitor
35
+ def initialize listener, options = {}
36
+ @excluded = compile_regexp(options[:excluded])
37
+ @excluded_files = compile_regexp(options[:excluded_files])
38
+ @excluded_directories = compile_regexp(options[:excluded_directories])
39
+ @included = compile_regexp(options[:included])
40
+ @included_files = compile_regexp(options[:included_files])
41
+ @included_directories = compile_regexp(options[:included_directories])
42
+ @recurse = options[:max_depth] || -1
43
+ self.next_visitor = listener
44
+ end
45
+ def enter_folder dir
46
+ return false unless include?(File.basename(dir), false)
47
+ if (result = super) && !descend?(dir)
48
+ leave_folder
49
+ return false
50
+ end
51
+ result
52
+ end
53
+ def visit_file name, full_path
54
+ return false unless include?(name, true)
55
+ super
56
+ end
57
+ protected
58
+ def compile_regexp(rx_list)
59
+ return nil if rx_list.nil? || rx_list.reject { |e| e.length == 0 }.empty?
60
+ Regexp.union(*rx_list.map { |e| /#{e}/i })
61
+ end
62
+ def descend?(name)
63
+ @recurse == -1 || @recurse >= (depth - 1)
64
+ end
65
+ def include?(name, is_file)
66
+ can_include = (@included.nil? || @included.match(name)) &&
67
+ ((is_file && (@included_files.nil? || @included_files.match(name))) ||
68
+ (!is_file && (@included_directories.nil? || @included_directories.match(name))))
69
+ if can_include
70
+ can_include = (@excluded.nil? || !@excluded.match(name)) &&
71
+ ((is_file && (@excluded_files.nil? || !@excluded_files.match(name))) ||
72
+ (!is_file && (@excluded_directories.nil? || !@excluded_directories.match(name))))
73
+ end
74
+ can_include
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,81 @@
1
+ require 'yaml'
2
+ require 'tempfile'
3
+ require 'shellwords'
4
+ begin
5
+ require 'platform'
6
+ rescue LoadError
7
+ end
8
+
9
+ module DTC::Utils
10
+ # Copied from gem utility_belt (and modified, so get your own copy http://utilitybelt.rubyforge.org)
11
+ # Giles Bowkett, Greg Brown, and several audience members from Giles' Ruby East presentation.
12
+ class InteractiveEditor
13
+ DEBIAN_SENSIBLE_EDITOR = "/usr/bin/sensible-editor"
14
+ MACOSX_OPEN_CMD = ["open", "--wait-apps", "-e"]
15
+ WIN_START_CMD = ["start", "-w"]
16
+ XDG_OPEN = "/usr/bin/xdg-open"
17
+ def self.sensible_editor
18
+ return Shellwords::split(ENV["VISUAL"]) if ENV["VISUAL"]
19
+ return Shellwords::split(ENV["EDITOR"]) if ENV["EDITOR"]
20
+ if defined?(Platform)
21
+ return WIN_START_CMD if Platform::IMPL == :mswin
22
+ return MACOSX_OPEN_CMD if Platform::IMPL == :macosx
23
+ if Platform::IMPL == :linux
24
+ if File.executable?(XDG_OPEN)
25
+ return XDG_OPEN
26
+ end
27
+ if File.executable?(DEBIAN_SENSIBLE_EDITOR)
28
+ return DEBIAN_SENSIBLE_EDITOR
29
+ end
30
+ end
31
+ end
32
+ raise "Could not determine what editor to use. Please specify (or use platform gem)."
33
+ end
34
+ attr_accessor :editor
35
+ def initialize(editor = InteractiveEditor.sensible_editor, extension = ".yaml")
36
+ @editor = @editor == "mate" ? ["mate", "-w"] : editor
37
+ @extension = extension
38
+ @file = nil
39
+ end
40
+ def filename
41
+ @file ? @file.path : nil
42
+ end
43
+ def edit_file_interactively(filename)
44
+ Exec.sys(@editor, filename)
45
+ end
46
+ def edit_interactively(content)
47
+ unless @file
48
+ @file = Tempfile.new(["#{File.basename(__FILE__, File.extname(__FILE__))}-edit", @extension])
49
+ @file << content
50
+ @file.close
51
+ end
52
+ edit_file_interactively(@file.path)
53
+ IO::read(@file.path)
54
+ rescue Exception => error
55
+ @file.unlink
56
+ @file = nil
57
+ puts error
58
+ end
59
+ def self.edit_file(filename, editor = InteractiveEditor.sensible_editor)
60
+ editor = InteractiveEditor.new editor
61
+ editor.edit_file_interactively(filename)
62
+ rescue Exception => error
63
+ puts "# !!!" + error.inspect
64
+ raise
65
+ return nil
66
+ end
67
+ def self.edit(content, extension = ".yaml", editor = InteractiveEditor.sensible_editor)
68
+ InteractiveEditor.new(editor, extension).edit_interactively(content)
69
+ end
70
+ def self.edit_in_yaml(object, editor = InteractiveEditor.sensible_editor)
71
+ input = "# Just empty and save this document to abort !\n" + object.to_yaml
72
+ editor = InteractiveEditor.new editor
73
+ res = editor.edit_interactively(input)
74
+ YAML::load(res)
75
+ rescue Exception => error
76
+ puts "# !!!" + error.inspect
77
+ raise
78
+ return nil
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,177 @@
1
+ module DTC
2
+ module Utils
3
+ # Small select(2) implementation
4
+ class MiniSelect
5
+ BUFSIZE = 4096
6
+ def initialize &block # :yields: miniselect
7
+ @want_read = {}
8
+ @want_write = {}
9
+ @want_close = {}
10
+ @shutdown = false
11
+ @stop_when_empty = true
12
+ yield self if block_given?
13
+ end
14
+ attr_reader :shutdown
15
+ # Default: True. Stops as soon as there are no more watched fds.
16
+ attr_accessor :stop_when_empty
17
+ # Shutdown the loop after this step
18
+ def stop
19
+ @shutdown = :requested_by_stop
20
+ end
21
+ # True if no items will be read or written (which also terminates the loop)
22
+ def empty?
23
+ @want_read.empty? && @want_write.empty?
24
+ end
25
+ # Add a file to monitor for reading.
26
+ # When read events occur, provided block is yielded with event <code>:read</code>,
27
+ # upon close it is yielded with event <code>:close</code>
28
+ def add_read fd, &cb # :yields: miniselect, event, file, data_string
29
+ @want_read[fd] = cb
30
+ end
31
+ # Write to specified fd in a non-blocking manner
32
+ #
33
+ # When write is completed, provided block is yielded with event <code>:done</code>,
34
+ # upon close it is yielded with event <code>:close</code>
35
+ def write fd, data, &cb # :yields: miniselect, event, file
36
+ raise "Cannot write, FD is closed" if fd.closed?
37
+ raise "Cannot write, FD is marked to close when finished" if @want_close[fd]
38
+ (@want_write[fd] ||= []) << [data, cb]
39
+ end
40
+ # Provided block is called at every select(2) timeout or operation
41
+ def every_beat &block # :yields: miniselect
42
+ (@block ||= []) << block
43
+ end
44
+ # Provided block is called at every select(2) timeout
45
+ def every_timeout &block # :yields: miniselect
46
+ (@timeouts ||= []) << block
47
+ end
48
+ # Provided block is called at every read/write error
49
+ def every_error &block # :yields: miniselect, fd, error
50
+ (@error_handlers ||= []) << block
51
+ end
52
+ # Close the specified file, calling all callbacks as required, closing the
53
+ # descriptor, and removing them from the miniselect
54
+ def close_now fd, error = nil
55
+ event_id = error ? :error : :close
56
+ Array(@want_read.delete(fd)).each { |cb| cb.call(self, event_id, fd, error) if cb }
57
+ Array(@want_write.delete(fd)).each { |data, cb| cb.call(self, event_id, fd, error) if cb }
58
+ Array(@want_close.delete(fd)).each { |cb| cb.call(self, event_id, fd, error) }
59
+ fd.close unless fd.closed?
60
+ if error && @error_handlers
61
+ Array(@error_handlers).each { |cb| cb.call(self, fd, error) }
62
+ end
63
+ end
64
+ # Write to specified fd in a non-blocking manner
65
+ #
66
+ # When write is completed, the file is closed and the provided
67
+ # block is yielded with event <code>:close</code>.
68
+ def close fd, &cb # :yields: miniselect, event, file
69
+ return if fd.closed?
70
+ @want_close[fd] = cb
71
+ if Array(@want_read[fd]).empty? && Array(@want_write[fd]).empty?
72
+ close_now fd
73
+ end
74
+ end
75
+ # Run the select loop, return when it terminates
76
+ def run timeout = 5
77
+ while !@shutdown && run_select(timeout)
78
+ Array(@block).each { |cb| cb.call(self) } if @block
79
+ end
80
+ end
81
+ # <code>self.new(&block).run(*args)</code>
82
+ def self.run *args, &block
83
+ self.new(&block).run(*args)
84
+ end
85
+
86
+ protected
87
+
88
+ # Read data from fd without blocking, close on error
89
+ #
90
+ # return [is_fd_active, data_read]
91
+ def read_fd_nonblock fd
92
+ res = ""
93
+ can_goon = true
94
+ error = nil
95
+ while fd
96
+ begin
97
+ buf = fd.read_nonblock(BUFSIZE)
98
+ res << buf if buf
99
+ redo if buf && buf.length == BUFSIZE
100
+ rescue Errno::EAGAIN
101
+ break
102
+ rescue EOFError => e
103
+ can_goon = false
104
+ break
105
+ rescue SystemCallError => e
106
+ can_goon = false
107
+ error = e
108
+ break
109
+ end
110
+ end
111
+ @want_read[fd].call(self, :read, fd, res) if res != ""
112
+ close_now(fd, error) unless can_goon
113
+ [can_goon, res]
114
+ end
115
+ # Write data to fd in a non blocking way, close on error
116
+ #
117
+ # return [is_fd_active, unwritten_data]
118
+ def write_fd_nonblock fd, data, cb
119
+ len = 0
120
+ can_goon = true
121
+ error = nil
122
+ if fd
123
+ begin
124
+ wlen = fd.write_nonblock(data)
125
+ len += wlen if wlen
126
+ rescue Errno::EAGAIN
127
+ rescue SystemCallError => e
128
+ error = e
129
+ can_goon = false
130
+ end
131
+ end
132
+ complete = len == data.length
133
+ if complete
134
+ pdata, cb = @want_write[fd].shift
135
+ cb.call(self, :done, fd) if cb
136
+ elsif len > 0
137
+ @want_write[fd].first[0] = data[len..-1]
138
+ end
139
+ close_now(fd, error) unless can_goon
140
+ [can_goon, can_goon && complete]
141
+ end
142
+ # run one call to select, and resulting fd operations
143
+ def run_select timeout
144
+ if @stop_when_empty && empty?
145
+ @shutdown = :no_active_fd
146
+ return false
147
+ end
148
+ result = select(@want_read.keys, @want_write.keys,
149
+ (@want_read.keys + @want_write.keys + @want_close.keys).uniq, timeout)
150
+ if !result
151
+ Array(@timeouts).each { |cb| cb.call(self) } if @timeouts
152
+ return true
153
+ end
154
+ r, w, e = *result
155
+ r.each do |readable|
156
+ can_retry, data = read_fd_nonblock(readable)
157
+ end
158
+ w.each do |writable|
159
+ queue = @want_write[writable]
160
+ while !queue.empty?
161
+ can_retry, is_write_complete = write_fd_nonblock(writable, *queue.first)
162
+ break unless can_retry && is_write_complete
163
+ end
164
+ if queue.empty?
165
+ @want_write.delete(writable)
166
+ close_now writable if @want_close[writable]
167
+ end
168
+ end
169
+ e.each do |erroredified|
170
+ puts "#{erroredified} error"
171
+ close erroredified
172
+ end
173
+ true
174
+ end
175
+ end
176
+ end
177
+ end
data/lib/dtc/utils.rb ADDED
@@ -0,0 +1,9 @@
1
+ module DTC
2
+ module Utils
3
+ autoload :Exec, 'dtc/utils/exec'
4
+ autoload :InteractiveEditor, 'dtc/utils/interactive_edit'
5
+ autoload :MiniSelect, 'dtc/utils/mini_select'
6
+ autoload :DSLDSL, 'dtc/utils/dsldsl'
7
+ autoload :FileVisitor, 'dtc/utils/file_visitor'
8
+ end
9
+ end