spud 0.1.18 → 0.1.19

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,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
+ qualified_name = [qualified_prefix(@filename), task.to_s].join('.').gsub(/^\./, '')
34
+ new(name: qualified_name, filename: @filename, &block)
35
+ end
36
+
37
+ # @param filename [String]
38
+ # @param task [String]
39
+ # @param positional [Array]
40
+ # @param named [Hash]
41
+ def self.invoke(filename, task, positional, named)
42
+ task = task.to_s
43
+ task_obj = task_for(filename, task)
44
+ if task_obj
45
+ Runtime.invoke(task_obj.name, positional, named)
46
+ else
47
+ Runtime.invoke(task, positional, named)
48
+ end
49
+ end
50
+
51
+ # @param filename [String]
52
+ # @param task [String]
53
+ def self.task_for(filename, task)
54
+ task = task.to_s
55
+ if task.include?('.')
56
+ Runtime.tasks[task]
57
+ else
58
+ Runtime.tasks[[qualified_prefix(filename), task.to_s].join('.').gsub(/^\./, '')]
59
+ end
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