cli-template 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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