rake-toolkit_program 0.1.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,158 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright 2019 PayTrace, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # This file defines the bash completion behavior of the rake-toolkit_program
19
+ # library.
20
+
21
+ module Rake
22
+ module ToolkitProgram
23
+ def self.each_completion_script_line(static_options: nil, static_flags: nil)
24
+ options = static_options || %Q{$("$1" --commands)}
25
+ flags = (static_flags.shellescape if static_flags) || \
26
+ ('' if static_options) || \
27
+ %Q{$("$1" --flag-completion "${COMP_WORDS[@]}")}
28
+
29
+ <<-END_BASH.dedent.each_line {|l| yield(" " + l)}
30
+ COMPREPLY=()
31
+ MY_WORDNUM=1
32
+ if [ "${COMP_CWORD}" = 2 ] && [ "${COMP_WORDS[1]}" = help ]; then
33
+ MY_WORDNUM=2
34
+ elif [ "${COMP_CWORD}" != "1" ]; then
35
+ HELP_FLAG="--help"
36
+ if [ -n "${COMP_WORDS[$COMP_CWORD]}" ] && [ "${HELP_FLAG\#${COMP_WORDS[$COMP_CWORD]}}" = "$HELP_FLAG" ]; then
37
+ # Word being completed is NOT a prefix of --help: don't offer --help
38
+ :
39
+ elif ! { echo " ${COMP_WORDS[*]}" | egrep -q '\\s(--help|-h|--\\s)'; }; then
40
+ COMPREPLY=("--help")
41
+ fi
42
+ DO_COMPGEN=true
43
+ if ! { echo " ${COMP_WORDS[*]}" | egrep -q '\\s(--help|-h|--\\s)'; }; then
44
+ FLAGS_CANDIDATE=#{flags}
45
+ if [ "$(echo "$FLAGS_CANDIDATE" | head -n1)" == '!NOFSCOMP!' ]; then
46
+ DO_COMPGEN=false
47
+ COMPREPLY+=($(echo "$FLAGS_CANDIDATE" | tail -n+2))
48
+ else
49
+ COMPREPLY+=($FLAGS_CANDIDATE)
50
+ fi
51
+ fi
52
+ if $DO_COMPGEN && [ "${COMP_WORDS[$COMP_CWORD]}" != "--" ] && ! { echo " ${COMP_WORDS[*]}" | egrep -q '\\s(--help|-h)'; }; then
53
+ COMPREPLY+=($(compgen -f -d -- "${COMP_WORDS[$COMP_CWORD]}"))
54
+ fi
55
+ return
56
+ fi
57
+ COMPREPLY=($(compgen -W "#{options}" -- "${COMP_WORDS[$MY_WORDNUM]}"))
58
+ END_BASH
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+ Rake::ToolkitProgram.command_tasks do # define task for shell completion
65
+ task '--commands' do
66
+ cmds = Rake::ToolkitProgram.available_commands(include: :listable)
67
+ puts(cmds.map do |t|
68
+ t.name[(Rake::ToolkitProgram::NAMESPACE.length + 1)..-1]
69
+ end.join(' '))
70
+ end
71
+
72
+ task '--flag-completion' do
73
+ program = Rake::ToolkitProgram
74
+ begin
75
+ script_path, command_name, *args = program.args
76
+ command = program.find(command_name)
77
+ next unless program::ArgParsingTask === command
78
+ incomplete = args.pop
79
+ candidate = command.argument_parser.candidate(incomplete.empty? ? '-' : incomplete)
80
+ begin
81
+ command.argument_parser.parse(args)
82
+ rescue OptionParser::MissingArgument
83
+ # We can't help complete flags because there is a flag that requires an
84
+ # argument, but it's fine to build more
85
+ rescue program::WrongArgumentCount
86
+ # args doesn't have the right number of arguments, and adding flags
87
+ # won't help, but it's fine to build more arguments -- so far, but see below
88
+ rescue OptionParser::ParseError
89
+ puts '!NOFSCOMP!'
90
+ else
91
+ if incomplete.empty? && !command.argument_parser.positional_cardinality_ok?(args.length + 1)
92
+ puts '!NOFSCOMP!'
93
+ end
94
+ candidate.select! {|c| c.start_with?('--')}
95
+ candidate = candidate.collect_concat do |c|
96
+ if c =~ /^--\[(\w+-)\](.*)/
97
+ ["--#{$2}", "--#{$1}#{$2}"]
98
+ else
99
+ [c]
100
+ end
101
+ end
102
+ puts candidate
103
+ end
104
+ rescue StandardError
105
+ end
106
+ end
107
+
108
+ task '--install-completions' do
109
+ script_path = Pathname(
110
+ Rake::ToolkitProgram.script_name(placeholder_ok: false)
111
+ )
112
+
113
+ profile_path, completions_dir = case Process.euid
114
+ when 0
115
+ ["/etc/profile", "/usr/local/lib/"]
116
+ else
117
+ ["~/.bash_profile", "~/.bash-complete"]
118
+ end.map {|path_str| Pathname(path_str).expand_path}
119
+ name_parts = [script_path.basename.to_s, 'completions']
120
+ completions_fpath = completions_dir / name_parts.join('-')
121
+
122
+ completions_fpath.dirname.mkpath
123
+ completions_fpath.open('w', 0644) do |out|
124
+ uniq_name = "_" + Random.new.bytes(20).unpack('H*').first
125
+ out.puts "#{uniq_name}() {"
126
+ Rake::ToolkitProgram.each_completion_script_line do |line|
127
+ out.puts line
128
+ end
129
+ out.puts "}"
130
+ out.puts "complete -F #{uniq_name} -o bashdefault #{script_path.basename}"
131
+ end
132
+
133
+ load_completions = %Q{source #{completions_fpath}}
134
+ load_completions_split = Shellwords.split(load_completions)
135
+
136
+ load_present = begin
137
+ profile_path.each_line.any? do |line|
138
+ begin
139
+ Shellwords.split(line, drop_comment: true)
140
+ rescue ArgumentError
141
+ []
142
+ end == load_completions_split
143
+ end
144
+ rescue Errno::ENOENT
145
+ false
146
+ end
147
+
148
+ if load_present
149
+ puts "Completions already installed in #{profile_path}"
150
+ else
151
+ profile_path.open('a') do |out|
152
+ out.puts load_completions
153
+ end
154
+ puts "Completions installed in #{profile_path}"
155
+ puts "Source #{completions_fpath} for immediate availability."
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,77 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright 2019 PayTrace, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # This file defines errors classes used by Rake::ToolkitProgram.
19
+
20
+ module Rake
21
+ module ToolkitProgram
22
+ class UnknownName < StandardError
23
+ include ProgramExitFromError
24
+ exit_with_code 2
25
+
26
+ def initialize(name)
27
+ super("The command '#{name}' is not known")
28
+ @name = name.to_s.dup.freeze
29
+ end
30
+
31
+ attr_reader :name
32
+
33
+ def print_error_message
34
+ s = ToolkitProgram.help_styling
35
+ $stderr.puts "#{s.error_marker '[ERROR]'} #{s.code name} is not a recognized command name."
36
+ $stderr.puts "Use #{s.code "#{ToolkitProgram.script_name} help"} for a list of available commands."
37
+ end
38
+ end
39
+
40
+ class NoCommand < StandardError
41
+ include ProgramExitFromError
42
+ exit_with_code 2
43
+
44
+ def print_error_message
45
+ s = ToolkitProgram.help_styling
46
+ $stderr.puts "#{s.error_marker '[ERROR]'} #{s.code name} A command is required."
47
+ $stderr.puts "Use #{s.code "#{ToolkitProgram.script_name} help"} for a list of available commands."
48
+ end
49
+ end
50
+
51
+ class InvalidCommandLine < StandardError
52
+ include ProgramExitFromError
53
+ exit_with_code 2
54
+
55
+ def print_error_message
56
+ s = ToolkitProgram.help_styling
57
+ $stderr.puts "#{s.error_marker 'ERROR'} #{self}"
58
+ end
59
+ end
60
+
61
+ class WrongArgumentCount < InvalidCommandLine
62
+ include ProgramExitFromError
63
+ exit_with_code 2
64
+
65
+ def initialize(cardinality_test, actual_count)
66
+ super(case cardinality_test
67
+ when Integer
68
+ "expected #{cardinality_test} arguments, got #{actual_count}"
69
+ when Range
70
+ "expected #{cardinality_test.to_inclusive} (inclusive) arguments, got #{actual_count}"
71
+ else
72
+ "#{actual_count} arguments given"
73
+ end)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,105 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright 2019 PayTrace, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # This file implements the 'help' command of rake-toolkit_program.
19
+
20
+ module Rake
21
+ module ToolkitProgram
22
+ command_tasks do # define 'help' command
23
+ task '-h' => :help
24
+ task '--help' => :help
25
+
26
+ help_styling {|s| desc <<-END_HELP.dedent
27
+ Show a list of commands or details of one command
28
+
29
+ To get help on a specific command, put the command's name as the first
30
+ argument after #{s.code('help')} or use #{s.code('-h')} or #{s.code('--help')} after the command's name.
31
+ END_HELP
32
+ }
33
+ task :help do
34
+ style = help_styling
35
+
36
+ puts
37
+ puts style.title(title)
38
+ puts
39
+
40
+ options_usage = "[#{style.param('OPTION ...')}]"
41
+ unless (task = find(args[0]))
42
+ puts "Usage: #{style.code(script_name)} #{style.param('COMMAND')} #{options_usage}"
43
+ puts
44
+ puts "Avaliable options vary depending on the command given. For details"
45
+ puts "of a particular command, use:"
46
+ puts
47
+ puts " #{style.code(script_name)} #{style.code('help')} #{style.param('COMMAND')}"
48
+ puts
49
+ puts "Commands:"
50
+ cmds = available_commands(include: :listable)
51
+ nsp_len = NAMESPACE.length + 1
52
+ name_field_length = cmds.map {|t| t.name.length - nsp_len}.max
53
+ cmds.each do |t|
54
+ puts " #{style.code(t.name[nsp_len..-1].rjust(name_field_length))} #{t.comment}"
55
+ end
56
+ puts
57
+ puts "Use #{style.code('help')} #{style.param('COMMAND')} to get more help on a specific command."
58
+ else
59
+ cmd_name = args[0]
60
+ arg_parser = (ArgParsingTask === task) ? task.argument_parser : nil
61
+ usage_parts = [
62
+ style.code(script_name),
63
+ style.code(cmd_name),
64
+ ]
65
+ option_count = arg_parser ? arg_parser.enum_for(:summarize).count : 0
66
+ usage_parts << options_usage if !arg_parser || option_count > 0
67
+ generic_arg_usage = "[#{style.param('ARG')} ...]"
68
+ if arg_parser
69
+ usage_parts.concat case arg_parser.positional_cardinality
70
+ when 0
71
+ []
72
+ when Integer
73
+ [style.param('ARG')] * arg_parser.positional_cardinality
74
+ when ->(card) {Range === card && card.to_inclusive == (0..1)}
75
+ ["[#{style.param('ARG')}]"]
76
+ when ->(card) {Range === card && card.begin > 0}
77
+ [generic_arg_usage[1..-2]]
78
+ else
79
+ [generic_arg_usage]
80
+ end
81
+ else
82
+ usage_parts << generic_arg_usage
83
+ end
84
+ puts "Usage: #{usage_parts.join(' ')}"
85
+ puts
86
+ puts task.full_comment
87
+ if arg_parser
88
+ if (pos_exp = arg_parser.positional_cardinality_explanation)
89
+ puts
90
+ puts pos_exp
91
+ end
92
+ end
93
+ if option_count > 0
94
+ puts
95
+ puts "Options:"
96
+ arg_parser.summarize do |optline|
97
+ puts optline
98
+ end
99
+ end
100
+ end
101
+ puts
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,68 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright 2019 PayTrace, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # This file defines the HelpStyling class, used to style the help output
19
+ # of a Rake::ToolkitProgram.
20
+
21
+ module Rake
22
+ module ToolkitProgram
23
+ ##
24
+ # An object that captures styling rules for a CLI program
25
+ #
26
+ # This class defines several methods that either configure a styling
27
+ # transformation or apply the configured transformation, depending on the
28
+ # argument type. These methods are defined with .define_style for
29
+ # consistency.
30
+ #
31
+ # Each "style" method (e.g. #title) "learns" how to apply its style if
32
+ # passed anything responding to #to_proc and applies its current style
33
+ # transformation if passed a string.
34
+ #
35
+ class HelpStyling
36
+ IDENTITY = -> (s) {s}
37
+
38
+ def initialize
39
+ super
40
+ begin
41
+ require 'colorize'
42
+ rescue LoadError
43
+ title ->(s) {"*** #{s} ***"}
44
+ else
45
+ title ->(s) {"*** #{s} ***".light_white.bold.on_blue}
46
+ code ->(s) {s.bold}
47
+ param ->(s) {s.italic}
48
+ error_marker ->(s) {s.bold.red.on_black}
49
+ end
50
+ end
51
+
52
+ def self.define_style(*names)
53
+ names.each do |name|
54
+ vname = "@#{name}".to_sym
55
+ define_method(name) do |s|
56
+ case
57
+ when String === s then (instance_variable_get(vname) || IDENTITY)[s]
58
+ when s.respond_to?(:to_proc) then instance_variable_set(vname, s.to_proc)
59
+ else raise ArgumentError, "\##{name} accepts a String or Proc"
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ define_style :title, :code, :param, :error_marker
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,83 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright 2019 PayTrace, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # This file defines the extensions to Rake::Task to support the
19
+ # rake-toolkit_program library.
20
+
21
+ module Rake
22
+ module ToolkitProgram
23
+ module TaskExt
24
+ ##
25
+ # Define argument parsing for a Rake::Task used as a command
26
+ #
27
+ # The block receives two arguments:
28
+ #
29
+ # 1. a Rake::ToolkitProgram::CommandOptionParser (a subclass of
30
+ # OptionParser)
31
+ # 2. the argument accumulator object passed in as +into+
32
+ #
33
+ # When the subject task is invoked through Rake::ToolkitProgram.run,
34
+ # the parser configured in this block is run against the _remaining_
35
+ # arguments (after the command name). The argument accumulator +into+
36
+ # will be available as Rake::ToolkitProgram.args instead of the default
37
+ # argument Array.
38
+ #
39
+ # Rake::ToolkitProgram::CommandOptionParser offers some useful extensions
40
+ # around positional parameters, since the Array containing the remaining
41
+ # arguments after parsing would be otherwise unavailable.
42
+ #
43
+ def parse_args(into: ToolkitProgram.new_default_parsed_args {Hash.new})
44
+ parser, new_args = CommandOptionParser.new(into), into
45
+ yield parser, new_args
46
+ @arg_parser = parser
47
+ extend ArgParsingTask
48
+ return self
49
+ end
50
+
51
+ ##
52
+ # Convenience method for raising Rake::ToolkitProgram::InvalidCommandLine
53
+ #
54
+ # The error raised is the standard error for an invalid command line
55
+ # when using Rake::ToolkitProgram.
56
+ #
57
+ def invalid_args!(message)
58
+ raise InvalidCommandLine, message
59
+ end
60
+
61
+ ##
62
+ # Prohibit any arguments when this command is invoked
63
+ #
64
+ # Help arguments are still allowed, as the special 'help' command is the
65
+ # one invoked when they are present.
66
+ #
67
+ def prohibit_args
68
+ parse_args(into: []) {|parser| parser.no_positional_args!}
69
+ end
70
+ end
71
+ Rake::Task.include TaskExt
72
+
73
+ module ArgParsingTask
74
+ def argument_parser
75
+ @arg_parser
76
+ end
77
+
78
+ def parsed_arguments
79
+ @arg_parser.argument_destination
80
+ end
81
+ end
82
+ end
83
+ end