git-commander 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +38 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +39 -0
- data/LICENSE.txt +21 -0
- data/README.md +196 -0
- data/Rakefile +7 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/git-cmd +35 -0
- data/git-commander.gemspec +31 -0
- data/lib/git_commander.rb +17 -0
- data/lib/git_commander/cli.rb +126 -0
- data/lib/git_commander/command.rb +168 -0
- data/lib/git_commander/command/configurator.rb +26 -0
- data/lib/git_commander/command/loaders/file_loader.rb +35 -0
- data/lib/git_commander/command/loaders/raw.rb +43 -0
- data/lib/git_commander/command/option.rb +43 -0
- data/lib/git_commander/command/runner.rb +45 -0
- data/lib/git_commander/command_loader_options.rb +34 -0
- data/lib/git_commander/loader.rb +28 -0
- data/lib/git_commander/loader_result.rb +18 -0
- data/lib/git_commander/logger.rb +39 -0
- data/lib/git_commander/plugin.rb +50 -0
- data/lib/git_commander/plugin/executor.rb +10 -0
- data/lib/git_commander/plugin/loader.rb +77 -0
- data/lib/git_commander/plugins/git.rb +31 -0
- data/lib/git_commander/plugins/github.rb +35 -0
- data/lib/git_commander/plugins/prompt.rb +8 -0
- data/lib/git_commander/plugins/system.rb +3 -0
- data/lib/git_commander/registry.rb +91 -0
- data/lib/git_commander/rspec.rb +3 -0
- data/lib/git_commander/rspec/plugin_helpers.rb +82 -0
- data/lib/git_commander/system.rb +76 -0
- data/lib/git_commander/version.rb +5 -0
- metadata +159 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/git_commander/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "git-commander"
|
7
|
+
spec.version = GitCommander::VERSION
|
8
|
+
spec.authors = ["Valentino Stoll"]
|
9
|
+
spec.email = ["v@codenamev.com"]
|
10
|
+
|
11
|
+
spec.summary = "Make your own git commands"
|
12
|
+
spec.description = "Build custom flexible git workflows with Ruby!"
|
13
|
+
spec.homepage = "https://github.com/codenamev/git-commander"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
19
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
end
|
21
|
+
spec.bindir = "exe"
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_development_dependency "byebug", "~> 11"
|
26
|
+
spec.add_development_dependency "rake", "~> 12.3"
|
27
|
+
spec.add_development_dependency "rdoc", "~> 6.2"
|
28
|
+
spec.add_development_dependency "rspec", "< 4.0"
|
29
|
+
|
30
|
+
spec.add_dependency "bundler", "~> 2.1", ">= 1.10.0"
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "git_commander/command"
|
4
|
+
require "git_commander/logger"
|
5
|
+
require "git_commander/plugin"
|
6
|
+
require "git_commander/registry"
|
7
|
+
require "git_commander/system"
|
8
|
+
require "git_commander/version"
|
9
|
+
|
10
|
+
# GitCommander is the global module used to house the global logger
|
11
|
+
module GitCommander
|
12
|
+
module_function
|
13
|
+
|
14
|
+
def logger(*args)
|
15
|
+
@logger ||= GitCommander::Logger.new(*args)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "registry"
|
4
|
+
require_relative "version"
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
module GitCommander
|
8
|
+
# Manages mapping commands and their arguments that are run from the command-line (via `git-cmd`)
|
9
|
+
# to their corresponding git-commander registered commands.
|
10
|
+
#
|
11
|
+
# @example Run a registered "start" command with a single argument
|
12
|
+
# GitCommander::CLI.new.run ["start", "new-feature""]
|
13
|
+
#
|
14
|
+
class CLI
|
15
|
+
attr_reader :output, :registry
|
16
|
+
|
17
|
+
# @param registry [GitCommander::Registry] (GitCommander::Registry.new) the
|
18
|
+
# command registry to use for matching available commands
|
19
|
+
# @param output [IO] (STDOUT) the IO object you want to use to send output to when running commands
|
20
|
+
def initialize(registry: GitCommander::Registry.new, output: STDOUT)
|
21
|
+
@registry = registry
|
22
|
+
@output = output
|
23
|
+
end
|
24
|
+
|
25
|
+
# Runs a GitCommander command
|
26
|
+
#
|
27
|
+
# @param args [Array] (ARGV) a list of arguments to pass to the registered command
|
28
|
+
def run(args = ARGV)
|
29
|
+
arguments = Array(args)
|
30
|
+
command = registry.find arguments.shift
|
31
|
+
options = parse_command_options!(command, arguments)
|
32
|
+
command.run options
|
33
|
+
rescue Registry::CommandNotFound
|
34
|
+
log_command_not_found(command)
|
35
|
+
|
36
|
+
help
|
37
|
+
rescue StandardError => e
|
38
|
+
say e.message
|
39
|
+
say e.backtrace
|
40
|
+
end
|
41
|
+
|
42
|
+
def say(message)
|
43
|
+
output.puts message
|
44
|
+
end
|
45
|
+
|
46
|
+
# Parses an array of values (as ARGV would provide) for the provided git-cmd command name.
|
47
|
+
# The +arguments+ are run through Ruby's [OptionParser] for validation and
|
48
|
+
# then filtered through the +command+ to extract it's options with any
|
49
|
+
# default values.
|
50
|
+
#
|
51
|
+
# @param command [Command] the git-cmd command to parse the arguments for
|
52
|
+
# @param arguments [Array] the command line arguments
|
53
|
+
# @return [Array<GitCommander::Command::Option>] the available options with values
|
54
|
+
def parse_command_options!(command, arguments)
|
55
|
+
parser = configure_option_parser_for_command(command)
|
56
|
+
parser.parse!(arguments)
|
57
|
+
|
58
|
+
# Add arguments to options to pass to defined commands
|
59
|
+
command.arguments.each do |argument|
|
60
|
+
argument.value = arguments.shift
|
61
|
+
end
|
62
|
+
|
63
|
+
command.options
|
64
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
|
65
|
+
command.help
|
66
|
+
GitCommander.logger.debug "[CLI] Failed to parse command line options – #{e.inspect}"
|
67
|
+
exit 1
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def help
|
73
|
+
say "NAME"
|
74
|
+
say " git-cmd – Git Commander allows running custom git commands from a centralized location."
|
75
|
+
say "VERSION"
|
76
|
+
say " #{GitCommander::VERSION}"
|
77
|
+
say "USAGE"
|
78
|
+
say " git-cmd command [command options] [arguments...]"
|
79
|
+
say "COMMANDS"
|
80
|
+
say registry.commands.keys.join(", ")
|
81
|
+
end
|
82
|
+
|
83
|
+
def log_command_not_found(command)
|
84
|
+
GitCommander.logger.error <<~ERROR_LOG
|
85
|
+
#{command} not found in registry. Available commands: #{registry.commands.keys.inspect}
|
86
|
+
ERROR_LOG
|
87
|
+
end
|
88
|
+
|
89
|
+
def configure_option_parser_for_command(command)
|
90
|
+
valid_arguments_for_command = command.arguments.map { |arg| "[#{arg.name}]" }.join(" ")
|
91
|
+
|
92
|
+
OptionParser.new do |opts|
|
93
|
+
opts.banner = "USAGE:\n git-cmd #{command.name} [command options] #{valid_arguments_for_command}"
|
94
|
+
opts.separator ""
|
95
|
+
opts.separator "COMMAND OPTIONS:" if command.flags.any? || command.switches.any?
|
96
|
+
|
97
|
+
configure_flags_for_option_parser_and_command(opts, command)
|
98
|
+
configure_switches_for_option_parser_and_command(opts, command)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def configure_flags_for_option_parser_and_command(option_parser, command)
|
103
|
+
command.flags.each do |flag|
|
104
|
+
option_parser.on("-#{flag.name[0]}", command_line_flag_formatted_name(flag), flag.description.to_s) do |f|
|
105
|
+
flag.value = f
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def configure_switches_for_option_parser_and_command(option_parser, command)
|
111
|
+
command.switches.each do |switch|
|
112
|
+
option_parser.on("-#{switch.name[0]}", "--[no-]#{switch.name}", switch.description) do |s|
|
113
|
+
switch.value = s
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def command_line_flag_formatted_name(flag)
|
119
|
+
"--#{underscore_to_kebab(flag.name)} #{flag.name.upcase}"
|
120
|
+
end
|
121
|
+
|
122
|
+
def underscore_to_kebab(sym_or_string)
|
123
|
+
sym_or_string.to_s.gsub("_", "-").to_sym
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require_relative "command/configurator"
|
5
|
+
require_relative "command/option"
|
6
|
+
require_relative "command/runner"
|
7
|
+
require_relative "command_loader_options"
|
8
|
+
|
9
|
+
module GitCommander
|
10
|
+
# Wraps domain logic for executing git-cmd Commands
|
11
|
+
class Command
|
12
|
+
include GitCommander::CommandLoaderOptions
|
13
|
+
|
14
|
+
attr_reader :arguments, :flags, :switches, :block, :name, :registry
|
15
|
+
attr_accessor :output
|
16
|
+
|
17
|
+
# @param name [String, Symbol] the name of the command
|
18
|
+
# @param registry [GitCommander::Registry] (GitCommander::Registry.new) the
|
19
|
+
# command registry to use lookups
|
20
|
+
# @param options [Hash] the options to create the command with
|
21
|
+
#
|
22
|
+
# @option options [String] :description (nil) a short description to use in the
|
23
|
+
# single line version of the command's help output
|
24
|
+
# @option options [String] :summary (nil) the long-form description of the command
|
25
|
+
# to use in the command's help output
|
26
|
+
# @option options [IO] :output (STDOUT) the IO object you want to use to
|
27
|
+
# send outut from the command to
|
28
|
+
# @option options [Array] :arguments an array of hashes describing the
|
29
|
+
# argument names and default values that can be supplied to the command
|
30
|
+
# @option options [Array] :flags an array of hashes describing the
|
31
|
+
# flags and default values that can be supplied to the command
|
32
|
+
# @option options [Array] :switches an array of hashes describing the
|
33
|
+
# switches and default values that can be supplied to the command
|
34
|
+
#
|
35
|
+
# @yieldparam [Array<Option>] run_options an Array of
|
36
|
+
# Option instances defined from the above +options+
|
37
|
+
#
|
38
|
+
def initialize(name, registry: nil, **options, &block)
|
39
|
+
@name = name
|
40
|
+
@description = options[:description]
|
41
|
+
@summary = options[:summary]
|
42
|
+
@block = block_given? ? block : proc {}
|
43
|
+
@registry = registry || GitCommander::Registry.new
|
44
|
+
@output = options[:output] || STDOUT
|
45
|
+
|
46
|
+
define_command_options(options)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Executes the block for the command with the provided run_options.
|
50
|
+
#
|
51
|
+
# @param run_options [Array<Option>] an array of Option(s) to pass to the {#block} of this Command
|
52
|
+
#
|
53
|
+
def run(run_options = [])
|
54
|
+
assign_option_values(run_options)
|
55
|
+
Runner.new(self).run options.map(&:to_h).reduce(:merge)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Appends the +message+ to the Command's {#output}
|
59
|
+
#
|
60
|
+
# @param message [String] the string to append to the {#output}
|
61
|
+
#
|
62
|
+
def say(message)
|
63
|
+
output.puts message
|
64
|
+
end
|
65
|
+
|
66
|
+
# Adds command-line help text to the {#output} of this Command
|
67
|
+
#
|
68
|
+
def help
|
69
|
+
say "NAME"
|
70
|
+
say " git-cmd #{name} – #{summary}"
|
71
|
+
say "USAGE"
|
72
|
+
say " git-cmd #{name} [command options] #{arguments.map { |arg| "[#{arg.name}]" }.join(" ")}"
|
73
|
+
description_help
|
74
|
+
argument_help
|
75
|
+
options_help
|
76
|
+
end
|
77
|
+
|
78
|
+
# Access to a unique Set of this Command's {#arguments}, {#flags}, and {#switches}
|
79
|
+
#
|
80
|
+
# @return [Set] a unique list of all options this command can accept
|
81
|
+
#
|
82
|
+
def options
|
83
|
+
Set.new(@arguments + @flags + @switches)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Add to this Command's {#arguments}, {#flags}, or {#switches}
|
87
|
+
#
|
88
|
+
# @param option_type [String, Symbol] the type of option to add
|
89
|
+
# @param options [Hash] the options to create the [Option] with
|
90
|
+
#
|
91
|
+
# @option options [String, Symbol] :name the name of the option to add
|
92
|
+
# @option options [Object] :default (nil) the default value of the Option
|
93
|
+
# @option options [String] :description (nil) a description of the Option to use in
|
94
|
+
# help text output
|
95
|
+
# @option options [Object] :value (nil) the value on of the Option
|
96
|
+
#
|
97
|
+
def add_option(option_type, options = {})
|
98
|
+
case option_type.to_sym
|
99
|
+
when :argument
|
100
|
+
@arguments << Option.new(**options)
|
101
|
+
when :flag
|
102
|
+
@flags << Option.new(**options)
|
103
|
+
when :switch
|
104
|
+
@switches << Option.new(**options)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def define_command_options(options)
|
111
|
+
@arguments = options_from_hash(options[:arguments])
|
112
|
+
@flags = options_from_hash(options[:flags])
|
113
|
+
@switches = options_from_hash(options[:switches])
|
114
|
+
end
|
115
|
+
|
116
|
+
def options_from_hash(hash)
|
117
|
+
Array(hash).map { |v| Option.new(**v) }
|
118
|
+
end
|
119
|
+
|
120
|
+
def assign_option_values(command_options)
|
121
|
+
options.each do |option|
|
122
|
+
command_option = command_options.find { |o| o.name == option.name }
|
123
|
+
next if command_option.nil?
|
124
|
+
|
125
|
+
option.value = command_option.value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def description_help
|
130
|
+
return if description.to_s.empty?
|
131
|
+
|
132
|
+
say "DESCRIPTION"
|
133
|
+
say " #{description}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def argument_help
|
137
|
+
return unless arguments.any?
|
138
|
+
|
139
|
+
say "ARGUMENTS"
|
140
|
+
arguments.each do |argument|
|
141
|
+
default_text = argument.default.nil? ? "" : "(default: #{argument.default.inspect}) "
|
142
|
+
say " #{argument.name} – #{default_text}#{argument.description}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def options_help
|
147
|
+
return unless flags.any? || switches.any?
|
148
|
+
|
149
|
+
say "COMMAND OPTIONS"
|
150
|
+
flag_help
|
151
|
+
switch_help
|
152
|
+
end
|
153
|
+
|
154
|
+
def flag_help
|
155
|
+
flags.each do |flag|
|
156
|
+
flag_names = ["-#{flag.name.to_s[0]}", "--#{flag.name}"]
|
157
|
+
say " #{flag_names} – #{flag.default.nil? ? "" : "(default: #{flag.default}) "}#{flag.description}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def switch_help
|
162
|
+
switches.each do |switch|
|
163
|
+
switch_names = [switch.name.to_s[0], "-#{switch.name}"].map { |s| "-#{s}" }.join(", ")
|
164
|
+
say " #{switch_names} – #{switch.default.nil? ? "" : "(default: #{switch.default}) "}#{switch.description}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitCommander
|
4
|
+
class Command
|
5
|
+
# Allows configuring a [GitCommander::Command] with a block
|
6
|
+
class Configurator
|
7
|
+
class ConfigurationError < StandardError; end
|
8
|
+
|
9
|
+
attr_reader :registry
|
10
|
+
|
11
|
+
def initialize(registry)
|
12
|
+
@registry = registry
|
13
|
+
end
|
14
|
+
|
15
|
+
def configure(name, &block)
|
16
|
+
new_command = GitCommander::Command.new(name, registry: registry)
|
17
|
+
new_command.instance_exec new_command, &block if block_given?
|
18
|
+
new_command
|
19
|
+
rescue StandardError => e
|
20
|
+
configuration_error = ConfigurationError.new(e.message)
|
21
|
+
configuration_error.set_backtrace e.backtrace
|
22
|
+
raise configuration_error
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../loader"
|
4
|
+
|
5
|
+
module GitCommander
|
6
|
+
class Command
|
7
|
+
module Loaders
|
8
|
+
# @abstract Handles loading commands from file
|
9
|
+
class FileLoader < ::GitCommander::Loader
|
10
|
+
class FileNotFoundError < StandardError; end
|
11
|
+
class FileLoadError < StandardError; end
|
12
|
+
|
13
|
+
attr_reader :filename
|
14
|
+
|
15
|
+
def load(filename)
|
16
|
+
raw_loader = Raw.new(registry)
|
17
|
+
@result = raw_loader.load(File.read(filename))
|
18
|
+
rescue Errno::ENOENT => e
|
19
|
+
handle_error FileNotFoundError, e
|
20
|
+
rescue StandardError => e
|
21
|
+
handle_error FileLoadError, e
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def handle_error(error_klass, original_error)
|
27
|
+
error = error_klass.new(original_error.message)
|
28
|
+
error.set_backtrace original_error.backtrace
|
29
|
+
@result.errors << error
|
30
|
+
@result
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../configurator"
|
4
|
+
require_relative "../../loader"
|
5
|
+
require_relative "../../plugin/loader"
|
6
|
+
|
7
|
+
module GitCommander
|
8
|
+
class Command
|
9
|
+
module Loaders
|
10
|
+
# @abstract Handles loading commands from raw strings
|
11
|
+
class Raw < ::GitCommander::Loader
|
12
|
+
class CommandParseError < StandardError; end
|
13
|
+
|
14
|
+
attr_reader :content
|
15
|
+
|
16
|
+
def load(content = "")
|
17
|
+
@content = content
|
18
|
+
instance_eval @content
|
19
|
+
result
|
20
|
+
# In this case, since we're evaluating raw IO in the context of this
|
21
|
+
# instance, we need to catch a wider range of exceptions. Otherwise,
|
22
|
+
# syntax errors would blow this up.
|
23
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
24
|
+
parse_error = CommandParseError.new(e.message)
|
25
|
+
parse_error.set_backtrace e.backtrace
|
26
|
+
result.errors << parse_error
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
def command(name, &block)
|
31
|
+
result.commands << Configurator.new(registry).configure(name, &block)
|
32
|
+
rescue Configurator::ConfigurationError => e
|
33
|
+
result.errors << e
|
34
|
+
end
|
35
|
+
|
36
|
+
def plugin(name, **options)
|
37
|
+
plugin_result = GitCommander::Plugin::Loader.new(registry).load(name, **options)
|
38
|
+
result.plugins |= plugin_result.plugins
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|