cli-template 3.0.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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +58 -0
  3. data/.gitignore +17 -0
  4. data/.rspec +4 -0
  5. data/CHANGELOG.md +48 -0
  6. data/Gemfile +6 -0
  7. data/Guardfile +11 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +58 -0
  10. data/Rakefile +32 -0
  11. data/cli-template.gemspec +32 -0
  12. data/exe/cli-template +9 -0
  13. data/lib/cli-template.rb +12 -0
  14. data/lib/cli-template/cli.rb +17 -0
  15. data/lib/cli-template/command.rb +47 -0
  16. data/lib/cli-template/help.rb +9 -0
  17. data/lib/cli-template/help/new.md +4 -0
  18. data/lib/cli-template/helpers.rb +12 -0
  19. data/lib/cli-template/new.rb +74 -0
  20. data/lib/cli-template/sequence.rb +45 -0
  21. data/lib/cli-template/version.rb +3 -0
  22. data/lib/templates/skeleton/%project_name%.gemspec.tt +30 -0
  23. data/lib/templates/skeleton/.gitignore +16 -0
  24. data/lib/templates/skeleton/.rspec +2 -0
  25. data/lib/templates/skeleton/CHANGELOG.md +7 -0
  26. data/lib/templates/skeleton/Gemfile.tt +6 -0
  27. data/lib/templates/skeleton/Guardfile +19 -0
  28. data/lib/templates/skeleton/LICENSE.txt +22 -0
  29. data/lib/templates/skeleton/README.md.tt +47 -0
  30. data/lib/templates/skeleton/Rakefile +6 -0
  31. data/lib/templates/skeleton/exe/%project_name%.tt +14 -0
  32. data/lib/templates/skeleton/lib/%project_name%.rb.tt +10 -0
  33. data/lib/templates/skeleton/lib/%underscored_name%/cli.rb.tt +170 -0
  34. data/lib/templates/skeleton/lib/%underscored_name%/command.rb.tt +164 -0
  35. data/lib/templates/skeleton/lib/%underscored_name%/help.rb.tt +9 -0
  36. data/lib/templates/skeleton/lib/%underscored_name%/help/hello.md.tt +3 -0
  37. data/lib/templates/skeleton/lib/%underscored_name%/help/sub/goodbye.md.tt +3 -0
  38. data/lib/templates/skeleton/lib/%underscored_name%/main.rb.tt +21 -0
  39. data/lib/templates/skeleton/lib/%underscored_name%/rake_command.rb.tt +63 -0
  40. data/lib/templates/skeleton/lib/%underscored_name%/sub.rb.tt +12 -0
  41. data/lib/templates/skeleton/lib/%underscored_name%/version.rb.tt +3 -0
  42. data/lib/templates/skeleton/spec/lib/cli_spec.rb.tt +24 -0
  43. data/lib/templates/skeleton/spec/spec_helper.rb.tt +29 -0
  44. data/spec/lib/cli_spec.rb +61 -0
  45. data/spec/spec_helper.rb +33 -0
  46. metadata +217 -0
@@ -0,0 +1,45 @@
1
+ require 'fileutils'
2
+ require 'colorize'
3
+ require 'active_support/core_ext/string'
4
+ require 'thor'
5
+ require 'bundler'
6
+
7
+ class CliTemplate::Sequence < Thor::Group
8
+ include Thor::Actions
9
+ include CliTemplate::Helpers
10
+
11
+ def self.source_root
12
+ File.expand_path("../../templates/skeleton", __FILE__)
13
+ end
14
+
15
+ private
16
+ def clone_project
17
+ unless git_installed?
18
+ abort "Unable to detect git installation on your system. Git needs to be installed in order to use the --repo option."
19
+ end
20
+
21
+ if File.exist?(project_name)
22
+ abort "The folder #{project_name} already exists."
23
+ else
24
+ run "git clone https://github.com/#{options[:repo]} #{project_name}"
25
+ end
26
+ confirm_cli_project
27
+ end
28
+
29
+ def confirm_cli_project
30
+ jets_project = File.exist?("#{project_name}/config/application.rb")
31
+ unless jets_project
32
+ puts "It does not look like the repo #{options[:repo]} is a jets project. Maybe double check that it is? Exited.".colorize(:red)
33
+ exit 1
34
+ end
35
+ end
36
+
37
+ def copy_project
38
+ puts "Creating new project called #{project_name}."
39
+ directory ".", project_name
40
+ end
41
+
42
+ def git_installed?
43
+ system("type git > /dev/null")
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module CliTemplate
2
+ VERSION = "3.0.0"
3
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "<%= underscored_name %>/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "<%= project_name %>"
8
+ spec.version = <%= project_class_name %>::VERSION
9
+ spec.authors = ["Tung Nguyen"]
10
+ spec.email = ["tongueroo@gmail.com"]
11
+ spec.description = "Generated with cli-template tool. Please write a gem description"
12
+ spec.summary = "Generated with cli-template tool. Please write a gem summary"
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "thor"
23
+ spec.add_dependency "colorize"
24
+ spec.add_dependency "rake"
25
+ spec.add_dependency "activesupport"
26
+
27
+ spec.add_development_dependency "bundler"
28
+ spec.add_development_dependency "byebug"
29
+ spec.add_development_dependency "rspec"
30
+ end
@@ -0,0 +1,16 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ InstalledFiles
7
+ _yardoc
8
+ coverage
9
+ doc/
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/tmp
15
+ test/version_tmp
16
+ tmp
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
@@ -0,0 +1,7 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ This project *tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
5
+
6
+ ## [0.1.0]
7
+ - Initial release.
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem dependencies in <%= project_name %>.gemspec
4
+ gemspec
5
+
6
+ gem "codeclimate-test-reporter", group: :test, require: nil
@@ -0,0 +1,19 @@
1
+ guard "bundler", cmd: "bundle" do
2
+ watch("Gemfile")
3
+ watch(/^.+\.gemspec/)
4
+ end
5
+
6
+ guard :rspec, cmd: "bundle exec rspec" do
7
+ require "guard/rspec/dsl"
8
+ dsl = Guard::RSpec::Dsl.new(self)
9
+
10
+ # RSpec files
11
+ rspec = dsl.rspec
12
+ watch(rspec.spec_helper) { rspec.spec_dir }
13
+ watch(rspec.spec_support) { rspec.spec_dir }
14
+ watch(rspec.spec_files)
15
+
16
+ # Ruby files
17
+ ruby = dsl.ruby
18
+ dsl.watch_spec_files_for(ruby.lib_files)
19
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Tung Nguyen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,47 @@
1
+ # <%= project_class_name %>
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/GEMNAME.png)](http://badge.fury.io/rb/GEMNAME)
4
+ [![CircleCI](https://circleci.com/gh/USER/REPO.svg?style=svg)](https://circleci.com/gh/USER/REPO)
5
+ [![Dependency Status](https://gemnasium.com/USER/REPO.png)](https://gemnasium.com/USER/REPO)
6
+ [![Coverage Status](https://coveralls.io/repos/USER/REPO/badge.png)](https://coveralls.io/r/USER/REPO)
7
+ [![Join the chat at https://gitter.im/USER/REPO](https://badges.gitter.im/USER/REPO.svg)](https://gitter.im/USER/REPO?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
8
+ [![Support](https://img.shields.io/badge/get-support-blue.svg)](https://boltops.com?utm_source=badge&utm_medium=badge&utm_campaign=cli-template)
9
+
10
+ TODO: Write a gem description
11
+
12
+ ## Usage
13
+
14
+ ```sh
15
+ <%= project_name %> hello yourname
16
+ <%= project_name %> sub:goodbye yourname
17
+ ```
18
+
19
+ The CLI tool also detects and tasks in the current folder's Rakefile and delegate to those tasks.
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```sh
26
+ gem "<%= project_name %>"
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ ```sh
32
+ bundle
33
+ ```
34
+
35
+ Or install it yourself as:
36
+
37
+ ```sh
38
+ gem install <%= project_name %>
39
+ ```
40
+
41
+ ## Contributing
42
+
43
+ 1. Fork it
44
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
45
+ 3. Commit your changes (`git commit -am "Add some feature"`)
46
+ 4. Push to the branch (`git push origin my-new-feature`)
47
+ 5. Create new Pull Request
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ task :default => :spec
5
+
6
+ RSpec::Core::RakeTask.new
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Trap ^C
4
+ Signal.trap("INT") {
5
+ puts "\nCtrl-C detected. Exiting..."
6
+ sleep 1
7
+ exit
8
+ }
9
+
10
+ $:.unshift(File.expand_path("../../lib", __FILE__))
11
+ require "<%= project_name %>"
12
+ require "<%= underscored_name %>/cli"
13
+
14
+ <%= project_class_name %>::CLI.start(ARGV)
@@ -0,0 +1,10 @@
1
+ $:.unshift(File.expand_path("../", __FILE__))
2
+ require "<%= underscored_name %>/version"
3
+
4
+ module <%= project_class_name %>
5
+ autoload :Help, "<%= underscored_name %>/help"
6
+ autoload :Command, "<%= underscored_name %>/command"
7
+ autoload :RakeCommand, "<%= underscored_name %>/rake_command"
8
+ autoload :CLI, "<%= underscored_name %>/cli"
9
+ autoload :Sub, "<%= underscored_name %>/sub"
10
+ end
@@ -0,0 +1,170 @@
1
+ require "thor"
2
+ require "active_support" # for autoload
3
+ require "active_support/core_ext"
4
+
5
+ class <%= project_class_name %>::CLI
6
+ def self.start(given_args=ARGV)
7
+ new(given_args).start
8
+ end
9
+
10
+ def self.thor_tasks
11
+ <%= project_class_name %>::Command.namespaced_commands
12
+ end
13
+
14
+ def initialize(given_args=ARGV, **config)
15
+ @given_args = given_args.dup
16
+ @config = config
17
+ end
18
+
19
+ def start
20
+ setup_auto_load
21
+ command_class = lookup(full_command)
22
+ if command_class
23
+ command_class.perform(full_command, thor_args)
24
+ else
25
+ main_help
26
+ end
27
+ end
28
+
29
+ def setup_auto_load
30
+ autoload_paths = [File.expand_path("../../", __FILE__)]
31
+ ActiveSupport::Dependencies.autoload_paths += autoload_paths
32
+ end
33
+
34
+ # thor_args normalized the args Array to work with our Thor command
35
+ # subclasses.
36
+ # 1. The namespace is stripe
37
+ # 2. Help is shifted in front if a help flag is detected
38
+ def thor_args
39
+ args = @given_args.clone
40
+
41
+ # Allow calling for help via:
42
+ # cli-template command help
43
+ # cli-template command -h
44
+ # cli-template command --help
45
+ # cli-template command -D
46
+ #
47
+ # as well thor's normal way:
48
+ #
49
+ # cli-template help command
50
+ help_args = args & help_flags
51
+ if help_args.empty?
52
+ args[0] = meth # reassigns the command without the namespace
53
+ else
54
+ # Allow using help flags at the end of the command to trigger help menu
55
+ args -= help_flags # remove "help" and help flags from args
56
+ args[0] = meth # first command will always be the meth now since
57
+ # we removed the help flags
58
+ args.unshift("help")
59
+ end
60
+ args.compact
61
+ end
62
+
63
+ def full_command
64
+ # Removes any args that starts with -, those are option args.
65
+ # Also remove "help" flag.
66
+ args = @given_args.reject {|o| o =~ /^-/ } - help_flags
67
+ command = args[0] # first argument should always be the command
68
+ <%= project_class_name %>::Command.autocomplete(command)
69
+ end
70
+
71
+ # 1. look up Thor tasks
72
+ # 2. look up Rake tasks
73
+ # 3. help menu with all commands when both Thor and Rake tasks are not found
74
+ def lookup(full_command)
75
+ thor_task_found = <%= project_class_name %>::Command.namespaced_commands.include?(full_command)
76
+ if thor_task_found
77
+ return <%= project_class_name %>::Command.klass_from_namespace(namespace)
78
+ end
79
+
80
+ rake_task_found = <%= project_class_name %>::RakeCommand.namespaced_commands.include?(full_command)
81
+ if rake_task_found
82
+ return <%= project_class_name %>::RakeCommand
83
+ end
84
+ end
85
+
86
+ def version_flags
87
+ ["--version", "-v"]
88
+ end
89
+
90
+ # ["-h", "-?", "--help", "-D", "help"]
91
+ def help_flags
92
+ Thor::HELP_MAPPINGS + ["help"]
93
+ end
94
+
95
+ def namespace
96
+ return nil unless full_command
97
+
98
+ if full_command.include?(':')
99
+ words = full_command.split(':')
100
+ words.pop
101
+ words.join(':')
102
+ end
103
+ end
104
+
105
+ def meth
106
+ return nil unless full_command
107
+
108
+ if full_command.include?(':')
109
+ full_command.split(':').pop
110
+ else
111
+ full_command
112
+ end
113
+ end
114
+
115
+ # Allow calling version via:
116
+ # <%= project_name %> version
117
+ # <%= project_name %> --version
118
+ # <%= project_name %> -v
119
+ def version_flag?
120
+ args = @given_args
121
+ args.length == 1 && !(args & version_flags).empty?
122
+ end
123
+
124
+ def main_help
125
+ if version_flag?
126
+ <%= project_class_name %>::Main.perform("version", ["version"])
127
+ return
128
+ end
129
+
130
+ shell = Thor::Shell::Basic.new
131
+ shell.say "Commands:"
132
+ shell.print_table(thor_list, :indent => 2, :truncate => true)
133
+
134
+ unless rake_list.empty?
135
+ shell.say "\nCommands via rake:"
136
+ shell.print_table(rake_list, :indent => 2, :truncate => true)
137
+ end
138
+
139
+ shell.say "\n"
140
+ shell.say main_help_body
141
+ end
142
+
143
+ def thor_list
144
+ <%= project_class_name %>::Command.help_list(show_all_tasks)
145
+ end
146
+
147
+ def rake_list
148
+ list = <%= project_class_name %>::RakeCommand.formatted_rake_tasks(show_all_tasks)
149
+ list.map do |array|
150
+ array[0] = "<%= project_name %> #{array[0]}"
151
+ array
152
+ end
153
+ end
154
+
155
+ def show_all_tasks
156
+ @given_args.include?("--all") || @given_args.include?("-A")
157
+ end
158
+
159
+ def main_help_body
160
+ <<-EOL
161
+ Add -h to any of the commands for more help. Examples:
162
+
163
+ <%= project_name %> hello -h
164
+ <%= project_name %> version
165
+ <%= project_name %> -h
166
+
167
+ EOL
168
+ end
169
+
170
+ end
@@ -0,0 +1,164 @@
1
+ require "thor"
2
+ require "active_support" # for autoload
3
+ require "active_support/core_ext"
4
+
5
+ # Override thor's long_desc identation behavior
6
+ # https://github.com/erikhuda/thor/issues/398
7
+ class Thor
8
+ module Shell
9
+ class Basic
10
+ def print_wrapped(message, options = {})
11
+ message = "\n#{message}" unless message[0] == "\n"
12
+ stdout.puts message
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ module <%= project_class_name %>
19
+ class Command < Thor
20
+ class << self
21
+ # thor_args is an array of commands. Examples:
22
+ # ["help"]
23
+ # ["dynamodb:migrate"]
24
+ #
25
+ # Same signature as RakeCommand.perform. Not using full_command.
26
+ #
27
+ # To call a thor task via ruby.
28
+ # Signature is a little weird with the repetition, examples:
29
+ #
30
+ # <%= project_class_name %>::Main.perform("hello", ["hello"])
31
+ # <%= project_class_name %>::Main.perform("dynamodb:migrate", ["migrate"])
32
+ #
33
+ def perform(full_command, thor_args)
34
+ config = {} # doesnt seem like config is used
35
+ dispatch(nil, thor_args, nil, config)
36
+ end
37
+
38
+ # Track all command subclasses.
39
+ def subclasses
40
+ @subclasses ||= []
41
+ end
42
+
43
+ def inherited(base)
44
+ super
45
+
46
+ if base.name
47
+ self.subclasses << base
48
+ end
49
+ end
50
+
51
+ # Useful for help menu when we need to have all the definitions loaded.
52
+ # Using constantize instead of require so we dont care about
53
+ # order. The eager load actually uses autoloading.
54
+ def eager_load!
55
+ path = File.expand_path("../../", __FILE__)
56
+ Dir.glob("#{path}/**/*.rb").select do |path|
57
+ next if !File.file?(path)
58
+
59
+ class_name = path
60
+ .sub(/\.rb$/,'')
61
+ .sub(%r{.*/lib/},'')
62
+ .classify
63
+
64
+ if class_name.include?('-')
65
+ puts "WARN: Unable to autoload a class with a dash in the name" if debug?
66
+ next
67
+ end
68
+
69
+ # special mapping cases
70
+ special_cases = {
71
+ "<%= project_class_name %>::Cli" => "<%= project_class_name %>::CLI",
72
+ "<%= project_class_name %>::Version" => "<%= project_class_name %>::VERSION",
73
+ }
74
+ class_name = special_cases[class_name] || class_name
75
+
76
+ puts "eager_load! loading path: #{path} class_name: #{class_name}" if debug?
77
+ class_name.constantize # dont have to worry about order.
78
+ end
79
+ end
80
+
81
+ # Fully qualifed task names. Examples:
82
+ # build
83
+ # process:controller
84
+ # dynamodb:migrate:down
85
+ def namespaced_commands
86
+ eager_load!
87
+ subclasses.map do |klass|
88
+ klass.all_tasks.keys.map do |task_name|
89
+ klass = klass.to_s.sub('<%= project_class_name %>::','')
90
+ namespace = klass =~ /^Main/ ? nil : klass.underscore.gsub('/',':')
91
+ [namespace, task_name].compact.join(':')
92
+ end
93
+ end.flatten.sort
94
+ end
95
+
96
+ # Use <%= project_class_name %> banner instead of Thor to account for namespaces in commands.
97
+ def banner(command, namespace = nil, subcommand = false)
98
+ namespace = namespace_from_class(self)
99
+ command_name = command.usage # set with desc when defining tht Thor class
100
+ namespaced_command = [namespace, command_name].compact.join(':')
101
+
102
+ "<%= project_name %> #{namespaced_command}"
103
+ end
104
+
105
+ def namespace_from_class(klass)
106
+ namespace = klass.to_s.sub('<%= project_class_name %>::', '').underscore.gsub('/',':')
107
+ namespace unless namespace == "main"
108
+ end
109
+
110
+ def help_list(all=false)
111
+ # hack to show hidden comands when requested
112
+ Thor::HiddenCommand.class_eval do
113
+ def hidden?; false; end
114
+ end if all
115
+
116
+ list = []
117
+ eager_load!
118
+ subclasses.each do |klass|
119
+ commands = klass.printable_commands(true, false)
120
+ commands.reject! { |array| array[0].include?(':help') }
121
+ list += commands
122
+ end
123
+
124
+ list.sort_by! { |array| array[0] }
125
+ end
126
+
127
+ def klass_from_namespace(namespace)
128
+ if namespace.nil?
129
+ <%= project_class_name %>::Main
130
+ else
131
+ class_name = namespace.gsub(':','/')
132
+ class_name = "<%= project_class_name %>::#{class_name.classify}"
133
+ class_name.constantize
134
+ end
135
+ end
136
+
137
+ # If this fails to match then it'l just return the original full command
138
+ def autocomplete(full_command)
139
+ return nil if full_command.nil? # <%= project_name %> help
140
+
141
+ eager_load!
142
+
143
+ words = full_command.split(':')
144
+ namespace = words[0..-2].join(':') if words.size > 1
145
+ command = words.last
146
+
147
+ # Thor's normalize_command_name autocompletes the command but then we need to add the namespace back
148
+ begin
149
+ thor_subclass = klass_from_namespace(namespace) # could NameError
150
+ command = thor_subclass.normalize_command_name(command) # could Thor::AmbiguousCommandError
151
+ [namespace, command].compact.join(':')
152
+ rescue NameError
153
+ full_command # return original full_command
154
+ rescue Thor::AmbiguousCommandError => e
155
+ full_command # return original full_command
156
+ end
157
+ end
158
+
159
+ def debug?
160
+ ENV['DEBUG'] && !ENV['TEST']
161
+ end
162
+ end
163
+ end
164
+ end