spud 0.1.16 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,52 @@
1
+ require 'stringio'
2
+ require 'open3'
3
+ require 'spud/build_tools/spud/shell/result'
4
+
5
+ module Spud
6
+ module BuildTools
7
+ module Spud
8
+ module Shell
9
+ class Command
10
+ attr_reader :result
11
+
12
+ # @param command [String]
13
+ # @param silent [Boolean]
14
+ # @param handle [IO]
15
+ def self.call(command, silent: false, handle: STDOUT)
16
+ cmd = new(command, silent: silent, handle: handle)
17
+ cmd.issue!
18
+ cmd.result
19
+ end
20
+
21
+ # @param command [String]
22
+ # @param silent [Boolean]
23
+ # @param handle [IO]
24
+ def initialize(command, silent: false, handle: STDOUT)
25
+ @command = command
26
+ @silent = silent
27
+ @handle = handle
28
+
29
+ @result = nil
30
+ end
31
+
32
+ # @return [void]
33
+ def issue!
34
+ capturer = StringIO.new
35
+
36
+ Open3.popen2e(@command) do |_stdin, stdout, thread|
37
+ loop do
38
+ line = stdout.gets
39
+ break unless line
40
+
41
+ @handle.write line unless @silent
42
+ capturer.puts line
43
+ end
44
+
45
+ @result = Result.new(capturer.string, thread.value)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,26 @@
1
+ module Spud
2
+ module BuildTools
3
+ module Spud
4
+ module Shell
5
+ class Result < String
6
+ attr_reader :status
7
+
8
+ # @param output [String]
9
+ # @param status [Process::Status]
10
+ def initialize(output, status)
11
+ super(output)
12
+ @status = status
13
+ end
14
+
15
+ def method_missing(symbol, *args)
16
+ status.respond_to?(symbol) ? status.send(symbol, *args) : super
17
+ end
18
+
19
+ def respond_to_missing?(symbol, *)
20
+ status.respond_to?(symbol) || super
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,125 @@
1
+ require 'spud/error'
2
+ require 'spud/task_args'
3
+ require 'spud/build_tools/task'
4
+ require 'spud/build_tools/spud/task'
5
+ require 'spud/build_tools/spud/dsl/file'
6
+ require 'spud/build_tools/spud/dsl/task'
7
+
8
+ module Spud
9
+ module BuildTools
10
+ module Spud
11
+ class Task < BuildTools::Task
12
+ # @return [void]
13
+ def self.mount!
14
+ Dir['**/Spudfile', '**/*.spud'].each do |filename|
15
+ @filename = filename
16
+ file_dsl.instance_eval(File.read(filename), filename)
17
+ end
18
+
19
+ @filename = nil
20
+ end
21
+
22
+ # @return [Spud::DSL::File]
23
+ def self.file_dsl
24
+ @file_dsl ||= DSL::File.new
25
+ end
26
+
27
+ # @param task [String]
28
+ # @param block [Proc]
29
+ # @return [void]
30
+ def self.add_task(task, &block)
31
+ raise "task '#{task}' somehow created without filename" unless @filename
32
+
33
+ new(name: qualified_name(@filename, task.to_s), filename: @filename, &block)
34
+ end
35
+
36
+ # @param filename [String]
37
+ # @param task [String]
38
+ # @param positional [Array]
39
+ # @param named [Hash]
40
+ def self.invoke(filename, task, positional, named)
41
+ task = task.to_s
42
+ task_obj = task_for(filename, task)
43
+ if task_obj
44
+ Runtime.invoke(task_obj.name, positional, named)
45
+ else
46
+ Runtime.invoke(task, positional, named)
47
+ end
48
+ end
49
+
50
+ # @param filename [String]
51
+ # @param task [String]
52
+ def self.task_for(filename, task)
53
+ Runtime.tasks[qualified_name(filename, task.to_s)]
54
+ end
55
+
56
+ def self.qualified_name(filename, task)
57
+ raise "task '#{task}' somehow created without filename" unless filename
58
+
59
+ [qualified_prefix(filename), task.to_s].join('.').gsub(/^\./, '')
60
+ end
61
+
62
+ # @param filename [String]
63
+ # @return [String]
64
+ def self.qualified_prefix(filename)
65
+ dirname = File.dirname(filename)
66
+ dirname = '' if dirname == '.'
67
+
68
+ basename = File.basename(filename, '.spud')
69
+ basename_array = basename == 'Spudfile' ? [] : [basename]
70
+
71
+ (dirname.split('/') + basename_array).join('.')
72
+ end
73
+
74
+ def initialize(name:, filename:, &block)
75
+ super
76
+ @block = block
77
+ end
78
+
79
+ # @param positional [Array]
80
+ # @param named [Hash]
81
+ # @return [Object]
82
+ def invoke(positional, named)
83
+ check_required!(positional)
84
+
85
+ return task_dsl.instance_exec(*positional, &@block) unless args.any_named?
86
+
87
+ task_dsl.instance_exec(*positional, **symbolize_keys(named), &@block)
88
+ rescue ArgumentError => error
89
+ raise Error, "invocation of '#{name}' with #{error.message}"
90
+ end
91
+
92
+ # @return [Spud::TaskArgs]
93
+ def args
94
+ @args ||= ::Spud::TaskArgs.from_block(filename, &@block)
95
+ end
96
+
97
+ private
98
+
99
+ # @param positional [Array<String>]
100
+ # @return [void]
101
+ def check_required!(positional)
102
+ required_positional = args.required_positional
103
+ missing_positional = required_positional.length - positional.length
104
+ if missing_positional > 0
105
+ arguments = required_positional.length - missing_positional > 1 ? 'arguments' : 'argument'
106
+ raise Error, "invocation of '#{name}' missing required #{arguments} #{required_positional.join(', ')}"
107
+ end
108
+ end
109
+
110
+ # @return [Spud::DSL::Task]
111
+ def task_dsl
112
+ @task_dsl ||= DSL::Task.new(filename)
113
+ end
114
+
115
+ # @param hash [Hash]
116
+ # @return [Hash]
117
+ def symbolize_keys(hash)
118
+ hash.each_with_object({}) do |(key, value), new_hash|
119
+ new_hash[key.is_a?(String) ? key.to_sym : key] = value
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,33 @@
1
+ module Spud
2
+ module BuildTools
3
+ class Task
4
+ # @return [String]
5
+ attr_reader :name
6
+ # @return [String]
7
+ attr_reader :filename
8
+ # @return [Spud::TaskArgs]
9
+ attr_reader :args
10
+
11
+ # @return [void]
12
+ def self.mount!
13
+ raise NotImplementedError
14
+ end
15
+
16
+ # @param name [String]
17
+ # @param filename [String]
18
+ def initialize(name:, filename:)
19
+ @name = name
20
+ @filename = filename
21
+
22
+ Runtime.tasks[name.to_s] = self
23
+ end
24
+
25
+ # @param positional [Array]
26
+ # @param named [Hash]
27
+ # @return [Object]
28
+ def invoke(positional = [], named = {})
29
+ raise NotImplementedError
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,101 @@
1
+ require 'spud/cli/results'
2
+
3
+ module Spud
4
+ module CLI
5
+ class Parser
6
+ # @return [Spud::CLI::Results]
7
+ attr_reader :results
8
+
9
+ # @return [void]
10
+ def self.parse!
11
+ parse(ARGV)
12
+ end
13
+
14
+ # @param args [Array<String>]
15
+ # @return [Spud::CLI::Results]
16
+ def self.parse(args)
17
+ new(args).parse!
18
+ end
19
+
20
+ # @param args [Array<String>]
21
+ def initialize(args)
22
+ @args = args.dup
23
+ @results = Results.new
24
+ end
25
+
26
+ # @return [void]
27
+ def parse!
28
+ parse_arg! until done?
29
+ results
30
+ end
31
+
32
+ private
33
+
34
+ # @return [void]
35
+ def parse_arg!
36
+ if before_task_name?
37
+ flag? ? handle_option! : set_task_name!
38
+ else
39
+ if flag?
40
+ results.named[lstrip_hyphens(take!)] = take!
41
+ else
42
+ results.positional << take!
43
+ end
44
+ end
45
+ end
46
+
47
+ # @return [void]
48
+ def handle_option!
49
+ flag = take!
50
+ case flag
51
+ when '-h', '--help' then options.help = true
52
+ when '-f', '--files' then options.files = true
53
+ when '-w', '--watch' then options.watches << take!
54
+ when '--debug' then options.debug = true
55
+ else raise Error, "invalid option: '#{flag}'"
56
+ end
57
+ end
58
+
59
+ # @return [Spud::Options]
60
+ def options
61
+ results.options
62
+ end
63
+
64
+ # @param flag [String]
65
+ # @return [String]
66
+ def lstrip_hyphens(flag)
67
+ flag.gsub(/^-+/, '')
68
+ end
69
+
70
+ # @return [Boolean]
71
+ def before_task_name?
72
+ !results.task
73
+ end
74
+
75
+ # @return [void]
76
+ def set_task_name!
77
+ results.task = take!
78
+ end
79
+
80
+ # @return [String]
81
+ def take!
82
+ @args.shift
83
+ end
84
+
85
+ # @return [String]
86
+ def arg
87
+ @args.first
88
+ end
89
+
90
+ # @return [Boolean]
91
+ def flag?
92
+ arg.start_with?('-')
93
+ end
94
+
95
+ # @return [Boolean]
96
+ def done?
97
+ @args.empty?
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,25 @@
1
+ require 'spud/options'
2
+
3
+ module Spud
4
+ module CLI
5
+ class Results
6
+ # @return [String]
7
+ attr_accessor :task
8
+
9
+ # @return [Spud::Options]
10
+ def options
11
+ @options ||= Options.new
12
+ end
13
+
14
+ # @return [Array<String>]
15
+ def positional
16
+ @positional ||= []
17
+ end
18
+
19
+ # @return [Hash{String->String}]
20
+ def named
21
+ @named ||= {}
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,5 +1,6 @@
1
1
  module Spud
2
2
  class Error < StandardError
3
+ # @return [String]
3
4
  def message
4
5
  "spud: #{super}"
5
6
  end
@@ -0,0 +1,23 @@
1
+ require 'stringio'
2
+ require 'spud/version'
3
+
4
+ module Spud
5
+ module Help
6
+ # @return [void]
7
+ def self.print!
8
+ help = StringIO.new
9
+
10
+ help.puts "spud #{VERSION}"
11
+ help.puts
12
+ help.puts 'usage:'
13
+ help.puts ' spud [options] <task> [args]'
14
+ help.puts
15
+ help.puts 'options:'
16
+ help.puts ' -h, --help show this help dialog'
17
+ help.puts ' -f, --files list parsed files'
18
+ help.puts ' --debug run in debug mode'
19
+
20
+ puts help.string
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,86 @@
1
+ require 'stringio'
2
+
3
+ module Spud
4
+ class Lister
5
+ # @param tasks [Array<BuildTools::Task>]
6
+ def initialize(tasks)
7
+ @tasks = tasks
8
+ end
9
+
10
+ # @return [void]
11
+ def list!
12
+ builder = StringIO.new
13
+
14
+ @tasks.each do |task|
15
+ builder.write task.name.ljust(max_task_length)
16
+
17
+ if show_positional_args?
18
+ builder.write ' '
19
+ builder.write task.args.positional.join(' ').ljust(max_positional_string_length)
20
+ end
21
+
22
+ if show_named_args?
23
+ builder.write ' '
24
+ builder.write task.args.named.join(' ').ljust(max_named_string_length)
25
+ end
26
+
27
+ if show_filenames?
28
+ builder.write ' '
29
+ builder.write task.filename
30
+ end
31
+
32
+ builder.write "\n"
33
+ end
34
+
35
+ puts builder.string
36
+ end
37
+
38
+ # @return [void]
39
+ def list_filenames!
40
+ puts filenames.join("\n")
41
+ end
42
+
43
+ private
44
+
45
+ # @return [Integer]
46
+ def max_task_length
47
+ @max_task_length ||= @tasks.map { |task| task.name.length }.max
48
+ end
49
+
50
+ # @return [Integer]
51
+ def max_positional_string_length
52
+ @max_positional_string_length ||= @tasks
53
+ .map { |task| task.args.positional.join(' ') }
54
+ .map(&:length)
55
+ .max
56
+ end
57
+
58
+ # @return [Boolean]
59
+ def show_positional_args?
60
+ @show_positional_args ||= @tasks.any? { |task| task.args.any_positional? }
61
+ end
62
+
63
+ # @return [Integer]
64
+ def max_named_string_length
65
+ @max_named_string_length ||= @tasks
66
+ .map { |task| task.args.named.join(' ') }
67
+ .map(&:length)
68
+ .max
69
+ end
70
+
71
+ # @return [Boolean]
72
+ def show_named_args?
73
+ @show_named_args ||= @tasks.any? { |task| task.args.any_named? }
74
+ end
75
+
76
+ # @return [Array<String>]
77
+ def filenames
78
+ @filenames ||= @tasks.map(&:filename).uniq
79
+ end
80
+
81
+ # @return [Boolean]
82
+ def show_filenames?
83
+ filenames.length > 1
84
+ end
85
+ end
86
+ end