skippy 0.1.0.a

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6005a25ffc1f9757b4cf9656d97be15351463830
4
+ data.tar.gz: da9d85e6b9c72eeee60b18fed419722e13829a64
5
+ SHA512:
6
+ metadata.gz: fc7071ef383374ae27cc9fa15d15544dac943d07a53832f91712f8f8a44d9c1d7eeb0286babae6a3cfdb3b4d91b074a4693f2ab825d6f700961942d6fd5ec4c4
7
+ data.tar.gz: 0ce7d0c9935e0f607e1367062a297ccc83788efebea2330f282bd1d6f8608b4a72f5fb158a8f26a5f5b9e893c94ef9727e29c2b15039cc5cf7d835ebb81dd508
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ *.sublime-workspace
@@ -0,0 +1,4 @@
1
+ // Place your settings in this file to overwrite default and user settings.
2
+ {
3
+ "editor.wrappingColumn": 80
4
+ }
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in skippy.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Thomas Thomassen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,61 @@
1
+ # Skippy
2
+
3
+ Skippy is a Command Line Interface which aims to automate common developer tasks for SketchUp Ruby extension development.
4
+
5
+ It is currently in very early stage of development so many things are incomplete. Feedback and contributions are welcome.
6
+
7
+ Some of the main goals are:
8
+
9
+ * Automate common tasks
10
+ * Packaging the extension
11
+ * Running build scripts
12
+ * Easy interface to add per-project custom commands/tasks
13
+ * Quick initialization of new project with templates
14
+ * Library dependency management
15
+ * Pull in third-party modules into extension namespace
16
+ * Add/Remove/Update dependencies
17
+
18
+ ## Installation
19
+
20
+ $ gem install skippy
21
+
22
+ ## Usage
23
+
24
+ TODO: Write more detailed usage instructions here.
25
+
26
+ Install the gem on your system, afterwards the `skippy` command should become available.
27
+
28
+ ### Quick-Reference
29
+
30
+ Type `skippy` to list available commands.
31
+
32
+ Type `skippy help [COMMAND]` for more information on how to use each command.
33
+
34
+ Use `skippy new` to create a new project in the current folder.
35
+
36
+ You can add custom per-project commands to a `skippy` folder in your project. Look at `skippy/example.rb` for an example of a simple custom command.
37
+
38
+ ### Power of Thor
39
+
40
+ Skippy is built on [Thor](https://github.com/erikhuda/thor). Refer to [Thor's Website](http://whatisthor.com/) and [documentation](http://www.rubydoc.info/github/wycats/thor/index) for details on creating commands.
41
+
42
+ When creating Skippy command use the following replacements:
43
+
44
+ * Instead of class `Thor`, use `Skippy::Command`
45
+ * Instead of class `Thor::Group`, use `Skippy::Command::Group`
46
+
47
+ ## Development
48
+
49
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
50
+
51
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
52
+
53
+ ## Contributing
54
+
55
+ Bug reports and pull requests are welcome on GitHub at https://github.com/thomthom/skippy.
56
+
57
+
58
+ ## License
59
+
60
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
61
+
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,25 @@
1
+ {
2
+ "folders":
3
+ [
4
+ {
5
+ "path": "."
6
+ },
7
+ {
8
+ "path": "C:\\Users\\tthomas2\\SourceTree\\thor"
9
+ }
10
+ ],
11
+ "settings":
12
+ {
13
+ "default_encoding": "UTF-8",
14
+ "ensure_newline_at_eof_on_save": true,
15
+ "rulers":
16
+ [
17
+ 80
18
+ ],
19
+ "show_encoding": true,
20
+ "show_line_endings": true,
21
+ "tab_size": 2,
22
+ "translate_tabs_to_spaces": true,
23
+ "trim_trailing_white_space_on_save": true
24
+ }
25
+ }
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Set the program name explicitly. Otherwise Thor will use the filename in the
4
+ # banner for the command help.
5
+ $PROGRAM_NAME = 'skippy'
6
+
7
+ # TODO(thomthom): Temporary set for development without having to set the ENV.
8
+ # Thor on Windows will by default not use colors. Using Cmder colors will work.
9
+ # Also in the latest Windows 10 colors appear to work. Not sure how older
10
+ # versions behave.
11
+ ENV['THOR_SHELL'] = 'Color' if $stdout.isatty
12
+
13
+ # Thor require DL which under Windows and Ruby 2.0 will yield a warning:
14
+ # DL is deprecated, please use Fiddle
15
+ #
16
+ # To avoid this appearing every time this tool is invoked, warnings are
17
+ # temporarily suppressed.
18
+ begin
19
+ original_verbose = $VERBOSE
20
+ $VERBOSE = nil
21
+ require 'thor'
22
+ # The Thor::Runner also needs to be loaded, as Skippy::CLI will call many of
23
+ # of the same methods - in many cases emulating what it do.
24
+ require "thor/runner"
25
+ # This is also needed to be set in order for Thor's utilities to output
26
+ # command names correctly.
27
+ $thor_runner = true
28
+ ensure
29
+ $VERBOSE = original_verbose
30
+ end
31
+
32
+ # Load skippy components the bootstrapper needs.
33
+ require 'skippy/app'
34
+ Skippy::App.boot(__FILE__)
35
+
36
+ # Everything is ready to start the CLI.
37
+ require 'skippy/cli'
38
+ require 'skippy/error'
39
+ begin
40
+ Skippy::CLI.start
41
+ rescue Skippy::Error => error
42
+ Skippy::CLI.display_error(error)
43
+ end
@@ -0,0 +1,15 @@
1
+ require 'skippy/namespace'
2
+
3
+ class Debug < Skippy::Command
4
+
5
+ include Thor::Actions
6
+
7
+ desc 'clean', 'Cleans out project files'
8
+ def clean
9
+ say "Cleaning out project files..."
10
+ remove_file Skippy::Project::PROJECT_FILENAME
11
+ remove_dir 'src'
12
+ remove_dir 'skippy'
13
+ end
14
+
15
+ end
@@ -0,0 +1,97 @@
1
+ require 'json'
2
+
3
+ require 'skippy/app'
4
+ require 'skippy/group'
5
+ require 'skippy/project'
6
+ require 'skippy/template'
7
+
8
+ class New < Skippy::Command::Group
9
+
10
+ include Thor::Actions
11
+
12
+ argument :namespace,
13
+ :type => :string,
14
+ :desc => 'The namespace the extension will use'
15
+
16
+ class_option :template,
17
+ :type => :string,
18
+ :desc => 'The template used to generate the project files',
19
+ :default => 'standard'
20
+
21
+ source_paths << Skippy.app.resources
22
+
23
+ attr_reader :project
24
+
25
+ def initialize_project
26
+ @project = Skippy::Project.new(Dir.pwd)
27
+ if project.exist?
28
+ raise Skippy::Error, "A project already exist: #{project.filename}"
29
+ end
30
+ project.namespace = namespace
31
+ end
32
+
33
+ def validate_template
34
+ template_path = File.join(self.class.source_root, options[:template])
35
+ unless File.directory?(template_path)
36
+ raise Skippy::Error, %(Template "#{options[:template]}" not found)
37
+ end
38
+ end
39
+
40
+ def create_project_json
41
+ say ''
42
+ say 'Generating skippy.json...'
43
+ say ''
44
+ say project.filename
45
+ say project.to_json, :yellow
46
+ project.save
47
+ end
48
+
49
+ def compile_templates
50
+ say ''
51
+ say "Compiling template '#{options[:template]}'..."
52
+ say ''
53
+ directory(options[:template], 'src')
54
+ end
55
+
56
+ def create_extension_json
57
+ extension_info = {
58
+ name: project.name,
59
+ description: project.description,
60
+ creator: project.author,
61
+ copyright: project.copyright,
62
+ license: project.license,
63
+ product_id: project.namespace.to_a.join('_'),
64
+ version: "0.1.0",
65
+ build: "1",
66
+ }
67
+ json = JSON.pretty_generate(extension_info)
68
+ json_filename = "src/#{project.namespace.to_underscore}/extension.json"
69
+ create_file(json_filename, json)
70
+ end
71
+
72
+ def create_example_skippy_command
73
+ copy_file('commands/example.rb', 'skippy/example.rb')
74
+ end
75
+
76
+ def finalize
77
+ say ''
78
+ say "Project for #{namespace} created.", :green
79
+ end
80
+
81
+ # These are methods to be used by the template engine when it compiles the
82
+ # templates and expands the filenames.
83
+ no_commands do
84
+
85
+ # @return [String] The basename for the extension files.
86
+ def ext_name
87
+ project.namespace.to_underscore
88
+ end
89
+
90
+ end # no_commands
91
+
92
+ # Needed as base for Thor::Actions' file actions.
93
+ def self.source_root
94
+ Skippy.app.templates_source_path
95
+ end
96
+
97
+ end
@@ -0,0 +1,27 @@
1
+ class Template < Skippy::Command
2
+
3
+ desc 'list', 'List all known templates'
4
+ def list
5
+ say 'Available templates:', :yellow
6
+ templates = Skippy.app.templates
7
+ if templates.empty?
8
+ say ' No templates found'
9
+ else
10
+ templates.each { |template|
11
+ say " #{template.name}", :green
12
+ }
13
+ end
14
+ end
15
+ default_command(:list)
16
+
17
+ desc 'install', 'Install a new template'
18
+ def install(source)
19
+ raise Skippy::Error, 'Not implemented'
20
+ end
21
+
22
+ desc 'remove', 'Remove an installed template'
23
+ def remove(template_name)
24
+ raise Skippy::Error, 'Not implemented'
25
+ end
26
+
27
+ end
@@ -0,0 +1,14 @@
1
+ class Hello < Skippy::Command
2
+
3
+ desc 'world PERSON', 'Oh, hi there!'
4
+ def world(person)
5
+ say "Hello #{person}"
6
+ end
7
+ default_command(:world)
8
+
9
+ desc 'universe', 'Greets the universe in general'
10
+ def universe(person)
11
+ say "DARK IN HERE, ISN'T IT?"
12
+ end
13
+
14
+ end
@@ -0,0 +1,45 @@
1
+ #-------------------------------------------------------------------------------
2
+ #
3
+ # Author: <%= project.author %>
4
+ # Copyright: <%= project.copyright %>
5
+ # License: <%= project.license %>
6
+ #
7
+ #-------------------------------------------------------------------------------
8
+
9
+ require 'json'
10
+
11
+ require 'extensions.rb'
12
+ require 'sketchup.rb'
13
+
14
+ <%= project.namespace.open %>
15
+
16
+ file = __FILE__.dup
17
+ # Account for Ruby encoding bug under Windows.
18
+ file.force_encoding('UTF-8') if file.respond_to?(:force_encoding)
19
+ # Support folder should be named the same as the root .rb file.
20
+ folder_name = File.basename(file, '.*')
21
+
22
+ # Path to the root .rb file (this file).
23
+ PATH_ROOT = File.dirname(file).freeze
24
+
25
+ # Path to the support folder.
26
+ PATH = File.join(PATH_ROOT, folder_name).freeze
27
+
28
+ # Extension information.
29
+ extension_json_file = File.join(PATH, 'extension.json')
30
+ extension_json = File.read(extension_json_file)
31
+ EXTENSION = ::JSON.parse(extension_json, symbolize_names: true).freeze
32
+
33
+ unless file_loaded?(__FILE__)
34
+ loader = File.join(PATH, 'bootstrap')
35
+ @extension = SketchupExtension.new(EXTENSION[:name], loader)
36
+ @extension.description = EXTENSION[:description]
37
+ @extension.version = EXTENSION[:version]
38
+ @extension.copyright = EXTENSION[:copyright]
39
+ @extension.creator = EXTENSION[:creator]
40
+ Sketchup.register_extension(@extension, true)
41
+ end
42
+
43
+ <%= project.namespace.close %>
44
+
45
+ file_loaded(__FILE__)
@@ -0,0 +1,21 @@
1
+ require 'sketchup.rb'
2
+
3
+ module <%= project.namespace %>
4
+
5
+ unless file_loaded?(__FILE__)
6
+ menu = UI.menu('Plugins').add_submenu(EXTENSION[:name])
7
+ menu.add_item('Make Magic') { self.make_magic }
8
+ menu.add_separator
9
+ menu.add_item('Help...') { self.open_help }
10
+ file_loaded(__FILE__)
11
+ end
12
+
13
+ def self.make_magic
14
+ # Do magic here...
15
+ end
16
+
17
+ def self.open_help
18
+ UI.openURL(EXTENSION[:url])
19
+ end
20
+
21
+ end # module
@@ -0,0 +1,45 @@
1
+ #-------------------------------------------------------------------------------
2
+ #
3
+ # Author: <%= project.author %>
4
+ # Copyright: <%= project.copyright %>
5
+ # License: <%= project.license %>
6
+ #
7
+ #-------------------------------------------------------------------------------
8
+
9
+ require 'json'
10
+
11
+ require 'extensions.rb'
12
+ require 'sketchup.rb'
13
+
14
+ <%= project.namespace.open %>
15
+
16
+ file = __FILE__.dup
17
+ # Account for Ruby encoding bug under Windows.
18
+ file.force_encoding('UTF-8') if file.respond_to?(:force_encoding)
19
+ # Support folder should be named the same as the root .rb file.
20
+ folder_name = File.basename(file, '.*')
21
+
22
+ # Path to the root .rb file (this file).
23
+ PATH_ROOT = File.dirname(file).freeze
24
+
25
+ # Path to the support folder.
26
+ PATH = File.join(PATH_ROOT, folder_name).freeze
27
+
28
+ # Extension information.
29
+ extension_json_file = File.join(PATH, 'extension.json')
30
+ extension_json = File.read(extension_json_file)
31
+ EXTENSION = ::JSON.parse(extension_json, symbolize_names: true).freeze
32
+
33
+ unless file_loaded?(__FILE__)
34
+ loader = File.join(PATH, 'bootstrap')
35
+ @extension = SketchupExtension.new(EXTENSION[:name], loader)
36
+ @extension.description = EXTENSION[:description]
37
+ @extension.version = EXTENSION[:version]
38
+ @extension.copyright = EXTENSION[:copyright]
39
+ @extension.creator = EXTENSION[:creator]
40
+ Sketchup.register_extension(@extension, true)
41
+ end
42
+
43
+ <%= project.namespace.close %>
44
+
45
+ file_loaded(__FILE__)
@@ -0,0 +1,6 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <body>
4
+ Hello World
5
+ </body>
6
+ </html>
@@ -0,0 +1,24 @@
1
+ require 'sketchup.rb'
2
+
3
+ module <%= project.namespace %>
4
+
5
+ unless file_loaded?(__FILE__)
6
+ menu = UI.menu('Plugins').add_submenu(EXTENSION[:name])
7
+ menu.add_item('Open Dialog') { self.open_dialog }
8
+ menu.add_separator
9
+ menu.add_item('Help...') { self.open_help }
10
+ file_loaded(__FILE__)
11
+ end
12
+
13
+ def self.open_dialog
14
+ file_path = File.join(PATH, 'html', 'dialog.html')
15
+ @dialog = UI::WebDialog.new
16
+ @dialog.set_file(file_path)
17
+ @dialog.show
18
+ end
19
+
20
+ def self.open_help
21
+ UI.openURL(EXTENSION[:url])
22
+ end
23
+
24
+ end # module
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'bundler' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("bundler", "bundler")
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "skippy"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'rake' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("rake", "rake")
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'skippy' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("skippy", "skippy")
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'thor' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("thor", "thor")
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../app/boot'
@@ -0,0 +1,5 @@
1
+ require "skippy/version"
2
+
3
+ module Skippy
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,62 @@
1
+ require 'pathname'
2
+
3
+ require 'skippy/command'
4
+ require 'skippy/group'
5
+ require 'skippy/template'
6
+ require 'skippy/skippy'
7
+
8
+ class Skippy::App
9
+
10
+ # @param [String] boot_loader_path
11
+ # @return [Skippy::App]
12
+ def self.boot(boot_loader_path)
13
+ Skippy.app = Skippy::App.new(boot_loader_path)
14
+ Skippy.app.boot
15
+ end
16
+
17
+ attr_reader :path
18
+
19
+ # @param [String] boot_loader_path
20
+ def initialize(boot_loader_path)
21
+ @boot_loader_path = File.expand_path(boot_loader_path)
22
+ @path = File.dirname(@boot_loader_path)
23
+ end
24
+
25
+ def boot
26
+ boot_commands
27
+ end
28
+
29
+ def resources(item = nil)
30
+ resource = Pathname.new(File.join(path, 'resources'))
31
+ item ? resource.join(item) : resource
32
+ end
33
+
34
+ # @return [Array<String>]
35
+ def templates_source_path
36
+ Pathname.new(File.join(path, 'templates'))
37
+ end
38
+
39
+ def templates
40
+ result = []
41
+ templates_source_path.entries.each { |entry|
42
+ template_path = templates_source_path.join(entry)
43
+ next unless template_path.directory?
44
+ next if %[. ..].include?(entry.basename.to_s)
45
+ result << Skippy::Template.new(entry)
46
+ }
47
+ result
48
+ end
49
+
50
+ private
51
+
52
+ # @return [Array<String>] loaded files
53
+ def boot_commands
54
+ # Load the default skippy commands.
55
+ path_commands = File.join(path, 'commands')
56
+ commands_pattern = File.join(path_commands, '*.rb')
57
+ Dir.glob(commands_pattern) { |filename|
58
+ require filename
59
+ }
60
+ end
61
+
62
+ end
@@ -0,0 +1,191 @@
1
+ require 'skippy/app'
2
+ require 'skippy/command'
3
+ require 'skippy/group'
4
+ require 'skippy/project'
5
+ require 'skippy/version'
6
+
7
+ # The Skippy::CLI class emulates much of what Thor::Runner do. It takes care of
8
+ # finding skippy projects and loading commands.
9
+ #
10
+ # The difference is mainly in how skippy vs thor present the commands.
11
+ #
12
+ # TODO(thomthom): ...or should it?
13
+ # Thor let you install commands globally, where as skippy does not.
14
+ #
15
+ # Skippy will list all known commands when invoked without any arguments.
16
+ #
17
+ # The code in this class will often refer to thor - when things have been copied
18
+ # verbatim. Makes it easier to update if needed.
19
+ class Skippy::CLI < Skippy::Command
20
+
21
+ class << self
22
+
23
+ # @param [Skippy::Error] error
24
+ def display_error(error)
25
+ shell = Thor::Base.shell.new
26
+ message = " #{error.message} "
27
+ message = shell.set_color(message, :white)
28
+ message = shell.set_color(message, :on_red)
29
+ shell.error message
30
+ end
31
+
32
+ end # Class methods
33
+
34
+ default_command :list
35
+
36
+ # Verbatim copy from Thor::Runner:
37
+ # Override Thor#help so it can give information about any class and any method.
38
+ #
39
+ def help(meth = nil)
40
+ if meth && !self.respond_to?(meth)
41
+ initialize_thorfiles(meth)
42
+ klass, command = Thor::Util.find_class_and_command_by_namespace(meth)
43
+ self.class.handle_no_command_error(command, false) if klass.nil?
44
+ klass.start(["-h", command].compact, :shell => shell)
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ # Verbatim copy from Thor::Runner:
51
+ # If a command is not found on Thor::Runner, method missing is invoked and
52
+ # Thor::Runner is then responsible for finding the command in all classes.
53
+ #
54
+ def method_missing(meth, *args)
55
+ meth = meth.to_s
56
+ initialize_thorfiles(meth)
57
+ klass, command = Thor::Util.find_class_and_command_by_namespace(meth)
58
+ self.class.handle_no_command_error(command, false) if klass.nil?
59
+ args.unshift(command) if command
60
+ klass.start(args, :shell => shell)
61
+ end
62
+
63
+ # Verbatim copy from Thor::Runner:
64
+ desc "list [SEARCH]", "List the available #{$PROGRAM_NAME} commands (--substring means .*SEARCH)"
65
+ method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean
66
+ def list(search = "")
67
+ initialize_thorfiles
68
+
69
+ search = ".*#{search}" if options["substring"]
70
+ search = /^#{search}.*/i
71
+ group = options[:group] || "standard"
72
+
73
+ klasses = Thor::Base.subclasses.select do |k|
74
+ (options[:all] || k.group == group) && k.namespace =~ search
75
+ end
76
+
77
+ display_klasses(false, false, klasses)
78
+ end
79
+
80
+ private
81
+
82
+ # Based on Thor::Runner, with exception of program name.
83
+ def self.banner(command, all = false, subcommand = false)
84
+ "#{$PROGRAM_NAME} " + command.formatted_usage(self, all, subcommand)
85
+ end
86
+
87
+ # Verbatim copy from Thor::Runner:
88
+ def self.exit_on_failure?
89
+ true
90
+ end
91
+
92
+ # This is one of the places this runner differ from Thor::Runner. It will
93
+ # instead load files for the current project.
94
+ #
95
+ # TODO(thomthom): Original arguments kept around for now, so avoid altering
96
+ # the methods that calls this. It might be that these arguments might be
97
+ # useful for optimizations later.
98
+ def initialize_thorfiles(_relevant_to = nil, _skip_lookup = false)
99
+ project = Skippy::Project.new(Dir.pwd)
100
+ return unless project.exist?
101
+ project.command_files { |filename|
102
+ unless Thor::Base.subclass_files.keys.include?(File.expand_path(filename))
103
+ Thor::Util.load_thorfile(filename, nil, options[:debug])
104
+ end
105
+ }
106
+ end
107
+
108
+ def display_app_banner
109
+ program_name = shell.set_color($PROGRAM_NAME.capitalize, :green)
110
+ version = shell.set_color('version', :clear)
111
+ program_version = shell.set_color(Skippy::VERSION, :yellow)
112
+ say "#{program_name} #{version} #{program_version}"
113
+ end
114
+
115
+ # Based on Thor::Runner:
116
+ def display_klasses(with_modules = false, show_internal = false, klasses = Thor::Base.subclasses)
117
+ unless show_internal
118
+ klasses -= [
119
+ Thor, Thor::Runner, Thor::Group,
120
+ Skippy, Skippy::CLI, Skippy::Command, Skippy::Command::Group
121
+ ]
122
+ end
123
+
124
+ fail Error, "No #{$PROGRAM_NAME.capitalize} commands available" if klasses.empty?
125
+
126
+ list = Hash.new { |h, k| h[k] = [] }
127
+ groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
128
+
129
+ # Get classes which inherit from Thor
130
+ (klasses - groups).each { |k|
131
+ list[k.namespace.split(":").first] += k.printable_commands(false)
132
+ }
133
+
134
+ # Get classes which inherit from Thor::Base
135
+ groups.map! { |k| k.printable_commands(false).first }
136
+ # Thor:Runner put these under 'root', but here we just avoid any name at
137
+ # all together.
138
+ list[''] = groups
139
+
140
+ display_app_banner
141
+ say
142
+ say 'Available commands:', :yellow
143
+
144
+ # Align all command descriptions. This means computing a fixed width for
145
+ # the first column.
146
+ col_width = list.map { |_, rows|
147
+ rows.map { |col| col.first.size }.max || 0
148
+ }.max
149
+
150
+ # Order namespaces with default coming first
151
+ list = list.sort { |a, b|
152
+ a[0].sub(/^default/, "") <=> b[0].sub(/^default/, "")
153
+ }
154
+ list.each { |n, commands|
155
+ display_commands(n, commands, col_width) unless commands.empty?
156
+ }
157
+ end
158
+
159
+ # Based on Thor::Runner:
160
+ def display_commands(namespace, list, col_width)
161
+ list.sort! { |a, b| a[0] <=> b[0] }
162
+
163
+ say shell.set_color(namespace, :yellow, true) unless namespace.empty?
164
+
165
+ list.each { |row|
166
+ row[0] = shell.set_color(row[0], :green) + shell.set_color('', :clear)
167
+ }
168
+ # TODO(thomthom): For some reason the column appear as half the width.
169
+ # Not sure why, so for now we apply this hack.
170
+ # TODO(thomthom): Because of the odd issue with col_width mentioned in
171
+ # `display_klasses` the table isn't truncated. Can probably re-enable if
172
+ # the col_width issue is fixed.
173
+ #print_table(list, :truncate => true, :indent => 2, :colwidth => col_width)
174
+ width = (col_width + 2) * 2
175
+ print_table(list, :indent => 2, :colwidth => width)
176
+ end
177
+ alias_method :display_tasks, :display_commands
178
+
179
+ # Based on Thor::Runner, skipping the yaml stuff:
180
+ def show_modules
181
+ info = []
182
+ labels = %w[Modules Namespaces]
183
+
184
+ info << labels
185
+ info << ["-" * labels[0].size, "-" * labels[1].size]
186
+
187
+ print_table info
188
+ say ""
189
+ end
190
+
191
+ end
@@ -0,0 +1,15 @@
1
+ require 'thor'
2
+
3
+ module Skippy
4
+ class Command < Thor
5
+
6
+ protected
7
+
8
+ # Customize the banner as we don't care for the 'skippy' prefix for each
9
+ # item in the list.
10
+ def self.banner(command, namespace = nil, subcommand = false)
11
+ "#{command.formatted_usage(self, true, subcommand)}"
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ module Skippy
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,17 @@
1
+ require 'thor/group'
2
+
3
+ require 'skippy/command'
4
+
5
+ module Skippy
6
+ class Command::Group < Thor::Group
7
+
8
+ protected
9
+
10
+ # Customize the banner as we don't care for the 'skippy' prefix for each
11
+ # item in the list.
12
+ def self.banner
13
+ "#{self_command.formatted_usage(self, false)}"
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,55 @@
1
+ class Skippy::Namespace
2
+
3
+ def initialize(namespace)
4
+ unless valid?(namespace)
5
+ raise Skippy::Error, "'#{namespace}' is not a valid Ruby namespace"
6
+ end
7
+ @namespace = namespace
8
+ end
9
+
10
+ def basename
11
+ to_a.last
12
+ end
13
+
14
+ def open
15
+ @open ||= to_a.map { |part| "module #{part}" }.join("\n")
16
+ @open
17
+ end
18
+
19
+ def close
20
+ @close ||= to_a.reverse.map { |part| "end # module #{part}" }.join("\n")
21
+ @close
22
+ end
23
+
24
+ def to_a
25
+ parts(@namespace)
26
+ end
27
+
28
+ def to_name
29
+ basename_words(basename).join(' ')
30
+ end
31
+
32
+ def to_s
33
+ @namespace.dup
34
+ end
35
+
36
+ def to_underscore
37
+ basename_words(basename).map { |word| word.downcase }.join('_')
38
+ end
39
+
40
+ private
41
+
42
+ def basename_words(namespace_basename)
43
+ result = namespace_basename.scan(/[[:upper:]]+[[:lower:][:digit:]]*/)
44
+ result.empty? ? [namespace_basename.dup] : result
45
+ end
46
+
47
+ def parts(namespace)
48
+ namespace.split('::')
49
+ end
50
+
51
+ def valid?(namespace)
52
+ parts(namespace).all? { |part| /^[[:upper:]]/.match(part) }
53
+ end
54
+
55
+ end
@@ -0,0 +1,90 @@
1
+ require 'json'
2
+ require 'pathname'
3
+
4
+ require 'skippy/namespace'
5
+
6
+ class Skippy::Project
7
+
8
+ PROJECT_FILENAME = 'skippy.json'.freeze
9
+
10
+ attr_reader :name, :namespace, :path, :author, :copyright, :license
11
+ attr_accessor :description
12
+
13
+ # Initialize a project for the provided path. If the path is within a project
14
+ # path the base path of the project will be found. Otherwise it's assumed that
15
+ # the path is the base for a new project.
16
+ #
17
+ # @param [Pathname, String] path
18
+ def initialize(path)
19
+ @path = find_project_path(path) || Pathname.new(path)
20
+ @namespace = Skippy::Namespace.new('Untitled')
21
+ @name = ''
22
+ @description = ''
23
+ @author = 'Unknown'
24
+ @copyright = "Copyright (c) #{Time.now.year}"
25
+ @license = 'None'
26
+ end
27
+
28
+ # @yield [filename]
29
+ # @yieldparam [String] filename the path to custom Skippy command
30
+ def command_files(&block)
31
+ files_pattern = File.join(path, 'skippy', '**', '*.rb')
32
+ Dir.glob(files_pattern) { |filename|
33
+ block.call(filename)
34
+ }
35
+ end
36
+
37
+ # Checks if a project exist on disk. If not it's just transient.
38
+ def exist?
39
+ File.exist?(filename)
40
+ end
41
+
42
+ # Full path to the project's configuration file. This file may not exist.
43
+ # @return [String]
44
+ def filename
45
+ File.join(path, PROJECT_FILENAME)
46
+ end
47
+
48
+ # @return [String]
49
+ def name
50
+ @name.empty? ? namespace.to_name : @name
51
+ end
52
+
53
+ # @param [String] namespace
54
+ def namespace=(namespace)
55
+ @namespace = Skippy::Namespace.new(namespace)
56
+ end
57
+
58
+ # Commits the project to disk.
59
+ def save
60
+ File.write(filename, to_json)
61
+ end
62
+
63
+ # @return [String]
64
+ def to_json
65
+ project_config = {
66
+ namespace: namespace,
67
+ name: name,
68
+ description: description
69
+ }
70
+ JSON.pretty_generate(project_config)
71
+ end
72
+
73
+ private
74
+
75
+ # Finds the root of a project based on any path within the project.
76
+ #
77
+ # @param [String] path
78
+ # return [Pathname, nil]
79
+ def find_project_path(path)
80
+ pathname = Pathname.new(path)
81
+ loop do
82
+ project_file = pathname.join(PROJECT_FILENAME)
83
+ return pathname if project_file.exist?
84
+ break if pathname.root?
85
+ pathname = pathname.parent
86
+ end
87
+ nil
88
+ end
89
+
90
+ end
@@ -0,0 +1,9 @@
1
+ require 'skippy/version'
2
+
3
+ module Skippy
4
+
5
+ class << self
6
+ attr_accessor :app
7
+ end
8
+
9
+ end
@@ -0,0 +1,3 @@
1
+ module Skippy
2
+ VERSION = '0.1.0.a'.freeze
3
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'skippy/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "skippy"
8
+ spec.version = Skippy::VERSION
9
+ spec.authors = ["Thomas Thomassen"]
10
+ spec.email = ["thomas@thomthom.net"]
11
+
12
+ spec.summary = %q{CLI development tool for SketchUp extensions.}
13
+ spec.description = %q{Automate common tasks for SketchUp extension development, including managing library dependencies.}
14
+ spec.homepage = "https://github.com/thomthom/skippy"
15
+ spec.license = "MIT"
16
+
17
+ spec.required_ruby_version = ">= 2.0"
18
+
19
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
+ f.match(%r{^(test|spec|features)/})
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.13"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "minitest", "~> 5.0"
29
+
30
+ spec.add_dependency "thor"
31
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: skippy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.a
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Thomassen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-01-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thor
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Automate common tasks for SketchUp extension development, including managing
70
+ library dependencies.
71
+ email:
72
+ - thomas@thomthom.net
73
+ executables:
74
+ - skippy
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".gitignore"
79
+ - ".vscode/settings.json"
80
+ - Gemfile
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - Skippy.sublime-project
85
+ - app/boot.rb
86
+ - app/commands/debug.rb
87
+ - app/commands/new.rb
88
+ - app/commands/template.rb
89
+ - app/resources/commands/example.rb
90
+ - app/templates/standard/%ext_name%.rb.tt
91
+ - app/templates/standard/%ext_name%/main.rb.tt
92
+ - app/templates/webdialog/extension.rb.erb
93
+ - app/templates/webdialog/extension/html/dialog.html
94
+ - app/templates/webdialog/extension/main.rb.erb
95
+ - bin/bundler
96
+ - bin/console
97
+ - bin/rake
98
+ - bin/setup
99
+ - bin/skippy
100
+ - bin/thor
101
+ - exe/skippy
102
+ - lib/skippy.rb
103
+ - lib/skippy/app.rb
104
+ - lib/skippy/cli.rb
105
+ - lib/skippy/command.rb
106
+ - lib/skippy/error.rb
107
+ - lib/skippy/group.rb
108
+ - lib/skippy/namespace.rb
109
+ - lib/skippy/project.rb
110
+ - lib/skippy/skippy.rb
111
+ - lib/skippy/version.rb
112
+ - skippy.gemspec
113
+ homepage: https://github.com/thomthom/skippy
114
+ licenses:
115
+ - MIT
116
+ metadata: {}
117
+ post_install_message:
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '2.0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">"
129
+ - !ruby/object:Gem::Version
130
+ version: 1.3.1
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 2.0.3
134
+ signing_key:
135
+ specification_version: 4
136
+ summary: CLI development tool for SketchUp extensions.
137
+ test_files: []
138
+ has_rdoc: