claide-plugins 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +41 -0
- data/.rubocop.yml +4 -0
- data/.rubocop_cocoapods.yml +116 -0
- data/.tm_properties +2 -0
- data/.travis.yml +24 -0
- data/CHANGELOG.md +113 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +82 -0
- data/LICENSE +21 -0
- data/README.md +44 -0
- data/Rakefile +55 -0
- data/claide-plugins.gemspec +32 -0
- data/lib/claide/command/gem_helper.rb +120 -0
- data/lib/claide/command/gem_index_cache.rb +87 -0
- data/lib/claide/command/plugins.rb +47 -0
- data/lib/claide/command/plugins/create.rb +121 -0
- data/lib/claide/command/plugins/list.rb +34 -0
- data/lib/claide/command/plugins/search.rb +60 -0
- data/lib/claide/command/plugins_config.rb +35 -0
- data/lib/claide/command/plugins_helper.rb +134 -0
- data/lib/claide/executable.rb +116 -0
- data/lib/claide_plugin.rb +1 -0
- data/lib/claide_plugins.rb +3 -0
- data/spec/command/gem_helper_spec.rb +41 -0
- data/spec/command/gem_index_cache_spec.rb +38 -0
- data/spec/command/plugins/create_spec.rb +89 -0
- data/spec/command/plugins/list_spec.rb +29 -0
- data/spec/command/plugins/search_spec.rb +55 -0
- data/spec/command/plugins_helper_spec.rb +33 -0
- data/spec/command/plugins_spec.rb +45 -0
- data/spec/fixtures/claide-foo1.gemspec +10 -0
- data/spec/fixtures/claide-foo2.gemspec +9 -0
- data/spec/fixtures/plugins.json +22 -0
- data/spec/fixtures/unprefixed.gemspec +10 -0
- data/spec/spec_helper.rb +93 -0
- metadata +165 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'claide/command/plugins_helper'
|
2
|
+
require 'claide/command/gem_helper'
|
3
|
+
|
4
|
+
module CLAide
|
5
|
+
class Command
|
6
|
+
class Plugins
|
7
|
+
# The list subcommand. Used to list all known plugins
|
8
|
+
#
|
9
|
+
class List < Plugins
|
10
|
+
self.summary = 'List all known plugins'
|
11
|
+
self.description = <<-DESC
|
12
|
+
List all known plugins (according to the list
|
13
|
+
hosted on github.com/CocoaPods/cocoapods-plugins)
|
14
|
+
DESC
|
15
|
+
|
16
|
+
def self.options
|
17
|
+
super.reject { |option, _| option == '--silent' }
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
plugins = PluginsHelper.known_plugins
|
22
|
+
GemHelper.download_and_cache_specs if self.verbose?
|
23
|
+
|
24
|
+
name = CLAide::Plugins.config.name
|
25
|
+
UI.title "Available #{name} Plugins:" do
|
26
|
+
plugins.each do |plugin|
|
27
|
+
PluginsHelper.print_plugin plugin, self.verbose?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'claide/command/plugins_helper'
|
2
|
+
require 'claide/command/gem_helper'
|
3
|
+
require 'claide/command'
|
4
|
+
|
5
|
+
module CLAide
|
6
|
+
class Command
|
7
|
+
class Plugins
|
8
|
+
# The search subcommand.
|
9
|
+
# Used to search a plugin in the list of known plugins,
|
10
|
+
# searching into the name, author description fields
|
11
|
+
#
|
12
|
+
class Search < Plugins
|
13
|
+
self.summary = 'Search for known plugins'
|
14
|
+
self.description = <<-DESC
|
15
|
+
Searches plugins whose 'name' contains the given `QUERY`.
|
16
|
+
`QUERY` is a regular expression, ignoring case.
|
17
|
+
|
18
|
+
With `--full`, it also searches by 'author' and 'description'.
|
19
|
+
DESC
|
20
|
+
|
21
|
+
self.arguments = [
|
22
|
+
CLAide::Argument.new('QUERY', true),
|
23
|
+
]
|
24
|
+
|
25
|
+
def self.options
|
26
|
+
[
|
27
|
+
['--full', 'Search by name, author, and description'],
|
28
|
+
].concat(super.reject { |option, _| option == '--silent' })
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(argv)
|
32
|
+
@full_text_search = argv.flag?('full')
|
33
|
+
@query = argv.shift_argument unless argv.arguments.empty?
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate!
|
38
|
+
super
|
39
|
+
help! 'A search query is required.' if @query.nil? || @query.empty?
|
40
|
+
begin
|
41
|
+
/#{@query}/
|
42
|
+
rescue RegexpError
|
43
|
+
help! 'A valid regular expression is required.'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def run
|
48
|
+
plugins = PluginsHelper.matching_plugins(@query, @full_text_search)
|
49
|
+
GemHelper.download_and_cache_specs if self.verbose?
|
50
|
+
|
51
|
+
name = CLAide::Plugins.config.name
|
52
|
+
UI.title "Available #{name} Plugins matching '#{@query}':"
|
53
|
+
plugins.each do |plugin|
|
54
|
+
PluginsHelper.print_plugin plugin, self.verbose?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'claide'
|
2
|
+
|
3
|
+
module CLAide
|
4
|
+
module Plugins
|
5
|
+
class Configuration
|
6
|
+
# name of the plugin
|
7
|
+
attr_accessor :name
|
8
|
+
|
9
|
+
# prefix to use when searching for gems to load at runtime
|
10
|
+
attr_accessor :plugin_prefix
|
11
|
+
|
12
|
+
# url for JSON file that holds list of plugins to show when searching
|
13
|
+
attr_accessor :plugin_list_url
|
14
|
+
|
15
|
+
# url for repo that holds template to use when creating a new plugin
|
16
|
+
attr_accessor :plugin_template_url
|
17
|
+
|
18
|
+
def initialize(name = 'default name',
|
19
|
+
plugin_prefix = 'claide',
|
20
|
+
plugin_list_url = 'https://github.com/cocoapods/claide-plugins/something.json',
|
21
|
+
plugin_template_url = 'https://github.com/cocoapods/claide-plugins-template')
|
22
|
+
@name = name
|
23
|
+
@plugin_prefix = plugin_prefix
|
24
|
+
@plugin_list_url = plugin_list_url
|
25
|
+
@plugin_template_url = plugin_template_url
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
attr_accessor :config
|
31
|
+
end
|
32
|
+
# set a default configuration that will work with claide-plugins
|
33
|
+
self.config = Configuration.new
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'claide/command/gem_helper'
|
2
|
+
|
3
|
+
module CLAide
|
4
|
+
class Command
|
5
|
+
# This module is used by Command::Plugins::List
|
6
|
+
# and Command::Plugins::Search to download and parse
|
7
|
+
# the JSON describing the plugins list and manipulate it
|
8
|
+
#
|
9
|
+
module PluginsHelper
|
10
|
+
def self.plugins_raw_url
|
11
|
+
CLAide::Plugins.config.plugin_list_url
|
12
|
+
end
|
13
|
+
|
14
|
+
# Force-download the JSON
|
15
|
+
#
|
16
|
+
# @return [Hash] The hash representing the JSON with all known plugins
|
17
|
+
#
|
18
|
+
def self.download_json
|
19
|
+
UI.puts 'Downloading Plugins list...'
|
20
|
+
response = REST.get(plugins_raw_url)
|
21
|
+
if response.ok?
|
22
|
+
parse_json(response.body)
|
23
|
+
else
|
24
|
+
raise Informative, 'Could not download plugins list ' \
|
25
|
+
"from cocoapods-plugins: #{response.inspect}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# The list of all known plugins, according to
|
30
|
+
# the JSON hosted on github's cocoapods-plugins
|
31
|
+
#
|
32
|
+
# @return [Array] all known plugins, as listed in the downloaded JSON
|
33
|
+
#
|
34
|
+
def self.known_plugins
|
35
|
+
json = download_json
|
36
|
+
json['plugins']
|
37
|
+
end
|
38
|
+
|
39
|
+
# Filter plugins to return only matching ones
|
40
|
+
#
|
41
|
+
# @param [String] query
|
42
|
+
# A query string that corresponds to a valid RegExp pattern.
|
43
|
+
#
|
44
|
+
# @param [Bool] full_text_search
|
45
|
+
# false only searches in the plugin's name.
|
46
|
+
# true searches in the plugin's name, author and description.
|
47
|
+
#
|
48
|
+
# @return [Array] all plugins matching the query
|
49
|
+
#
|
50
|
+
def self.matching_plugins(query, full_text_search)
|
51
|
+
query_regexp = /#{query}/i
|
52
|
+
known_plugins.reject do |plugin|
|
53
|
+
texts = [plugin['name']]
|
54
|
+
if full_text_search
|
55
|
+
texts << plugin['author'] if plugin['author']
|
56
|
+
texts << plugin['description'] if plugin['description']
|
57
|
+
end
|
58
|
+
texts.grep(query_regexp).empty?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Display information about a plugin
|
63
|
+
#
|
64
|
+
# @param [Hash] plugin
|
65
|
+
# The hash describing the plugin
|
66
|
+
#
|
67
|
+
# @param [Bool] verbose
|
68
|
+
# If true, will also print the author of the plugins.
|
69
|
+
# Defaults to false.
|
70
|
+
#
|
71
|
+
def self.print_plugin(plugin, verbose = false)
|
72
|
+
plugin_colored_name = plugin_title(plugin)
|
73
|
+
|
74
|
+
UI.title(plugin_colored_name, '', 1) do
|
75
|
+
UI.puts_indented plugin['description']
|
76
|
+
ljust = verbose ? 16 : 11
|
77
|
+
UI.labeled('Gem', plugin['gem'], ljust)
|
78
|
+
UI.labeled('URL', plugin['url'], ljust)
|
79
|
+
print_verbose_plugin(plugin, ljust) if verbose
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
#----------------#
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Smaller helper to print out the verbose details
|
88
|
+
# for a plugin.
|
89
|
+
#
|
90
|
+
# @param [Hash] plugin
|
91
|
+
# The hash describing the plugin
|
92
|
+
#
|
93
|
+
# @param [Integer] ljust
|
94
|
+
# The left justification that is passed into UI.labeled
|
95
|
+
#
|
96
|
+
def self.print_verbose_plugin(plugin, ljust)
|
97
|
+
UI.labeled('Author', plugin['author'], ljust)
|
98
|
+
unless GemHelper.cache.specs.empty?
|
99
|
+
versions = GemHelper.versions_string(plugin['gem'])
|
100
|
+
UI.labeled('Versions', versions, ljust)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Parse the given JSON data, handling parsing errors if any
|
105
|
+
#
|
106
|
+
# @param [String] json_str
|
107
|
+
# The string representation of the JSON to parse
|
108
|
+
#
|
109
|
+
def self.parse_json(json_str)
|
110
|
+
JSON.parse(json_str)
|
111
|
+
rescue JSON::ParserError => e
|
112
|
+
raise Informative, "Invalid plugins list from cocoapods-plugins: #{e}"
|
113
|
+
end
|
114
|
+
|
115
|
+
# Format the title line to print the plugin info with print_plugin
|
116
|
+
# coloring it according to whether the plugin is installed or not
|
117
|
+
#
|
118
|
+
# @param [Hash] plugin
|
119
|
+
# The hash describing the plugin
|
120
|
+
#
|
121
|
+
# @return [String] The formatted and colored title
|
122
|
+
#
|
123
|
+
def self.plugin_title(plugin)
|
124
|
+
plugin_name = "-> #{plugin['name']}"
|
125
|
+
if GemHelper.gem_installed?(plugin['gem'])
|
126
|
+
plugin_name += " (#{GemHelper.installed_version(plugin['gem'])})"
|
127
|
+
plugin_name.green
|
128
|
+
else
|
129
|
+
plugin_name.yellow
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module CLAide
|
2
|
+
# Module which provides support for running executables.
|
3
|
+
#
|
4
|
+
# In a class it can be used as:
|
5
|
+
#
|
6
|
+
# extend Executable
|
7
|
+
# executable :git
|
8
|
+
#
|
9
|
+
# This will create two methods `git` and `git!` both accept a command but
|
10
|
+
# the latter will raise on non successful executions. The methods return the
|
11
|
+
# output of the command.
|
12
|
+
#
|
13
|
+
module Executable
|
14
|
+
# Creates the methods for the executable with the given name.
|
15
|
+
#
|
16
|
+
# @param [Symbol] name
|
17
|
+
# the name of the executable.
|
18
|
+
#
|
19
|
+
# @return [void]
|
20
|
+
#
|
21
|
+
def executable(name)
|
22
|
+
define_method(name) do |*command|
|
23
|
+
Executable.execute_command(name, Array(command).flatten, false)
|
24
|
+
end
|
25
|
+
|
26
|
+
define_method(name.to_s + '!') do |*command|
|
27
|
+
Executable.execute_command(name, Array(command).flatten, true)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Executes the given command. Displays output if in verbose mode.
|
32
|
+
#
|
33
|
+
# @param [String] bin
|
34
|
+
# The binary to use.
|
35
|
+
#
|
36
|
+
# @param [Array<#to_s>] command
|
37
|
+
# The command to send to the binary.
|
38
|
+
#
|
39
|
+
# @param [Bool] raise_on_failure
|
40
|
+
# Whether it should raise if the command fails.
|
41
|
+
#
|
42
|
+
# @raise If the executable could not be located.
|
43
|
+
#
|
44
|
+
# @raise If the command fails and the `raise_on_failure` is set to true.
|
45
|
+
#
|
46
|
+
# @return [String] the output of the command (STDOUT and STDERR).
|
47
|
+
#
|
48
|
+
# @todo Find a way to display the live output of the commands.
|
49
|
+
#
|
50
|
+
def self.execute_command(exe, command, raise_on_failure)
|
51
|
+
bin = `which #{exe}`.strip
|
52
|
+
raise Informative, "Unable to locate `#{exe}`" if bin.empty?
|
53
|
+
|
54
|
+
require 'open4'
|
55
|
+
require 'shellwords'
|
56
|
+
|
57
|
+
command = command.map(&:to_s)
|
58
|
+
full_command = \
|
59
|
+
"#{bin.shellescape} #{command.map(&:shellescape).join(' ')}"
|
60
|
+
|
61
|
+
# if Config.instance.verbose?
|
62
|
+
# UI.message("$ #{full_command}")
|
63
|
+
# stdout, stderr = Indenter.new(STDOUT), Indenter.new(STDERR)
|
64
|
+
# else
|
65
|
+
stdout, stderr = Indenter.new, Indenter.new
|
66
|
+
# end
|
67
|
+
|
68
|
+
options = { :stdout => stdout, :stderr => stderr, :status => true }
|
69
|
+
status = Open4.spawn(bin, command, options)
|
70
|
+
output = stdout.join("\n") + stderr.join("\n")
|
71
|
+
unless status.success?
|
72
|
+
if raise_on_failure
|
73
|
+
raise Informative, "#{full_command}\n\n#{output}"
|
74
|
+
else
|
75
|
+
UI.message("[!] Failed: #{full_command}".red)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
output
|
79
|
+
end
|
80
|
+
|
81
|
+
#-------------------------------------------------------------------------#
|
82
|
+
|
83
|
+
# Helper class that allows to write to an {IO} instance taking into account
|
84
|
+
# the UI indentation level.
|
85
|
+
#
|
86
|
+
class Indenter < ::Array
|
87
|
+
# @return [Fixnum] The indentation level of the UI.
|
88
|
+
#
|
89
|
+
attr_accessor :indent
|
90
|
+
|
91
|
+
# @return [IO] the {IO} to which the output should be printed.
|
92
|
+
#
|
93
|
+
attr_accessor :io
|
94
|
+
|
95
|
+
# @param [IO] io @see io
|
96
|
+
#
|
97
|
+
def initialize(io = nil)
|
98
|
+
@io = io
|
99
|
+
@indent = ' ' * UI.indentation_level
|
100
|
+
end
|
101
|
+
|
102
|
+
# Stores a portion of the output and prints it to the {IO} instance.
|
103
|
+
#
|
104
|
+
# @param [String] value
|
105
|
+
# the output to print.
|
106
|
+
#
|
107
|
+
# @return [void]
|
108
|
+
#
|
109
|
+
def <<(value)
|
110
|
+
super
|
111
|
+
ensure
|
112
|
+
@io << "#{ indent }#{ value }" if @io
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'claide/command/plugins'
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
2
|
+
require 'claide/command/gem_helper'
|
3
|
+
|
4
|
+
# The CLAide namespace
|
5
|
+
#
|
6
|
+
module CLAide
|
7
|
+
describe Command::GemHelper do
|
8
|
+
before do
|
9
|
+
UI_OUT.reopen
|
10
|
+
end
|
11
|
+
|
12
|
+
after do
|
13
|
+
mocha_teardown
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'detects if a gem is installed' do
|
17
|
+
Command::GemHelper.gem_installed?('bacon').should.be.true
|
18
|
+
Command::GemHelper.gem_installed?('fake-fake-fake-gem').should.be.false
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'detects if a specific version of a gem is installed' do
|
22
|
+
Command::GemHelper.gem_installed?('bacon', Bacon::VERSION).should.be.true
|
23
|
+
impossibacon = Gem::Version.new(Bacon::VERSION).bump
|
24
|
+
Command::GemHelper.gem_installed?('bacon', impossibacon).should.be.false
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'creates a version list that includes all versions of a single gem' do
|
28
|
+
spec2 = Gem::NameTuple.new('cocoapods-plugins', Gem::Version.new('0.2.0'))
|
29
|
+
spec1 = Gem::NameTuple.new('cocoapods-plugins', Gem::Version.new('0.1.0'))
|
30
|
+
response = [{ 1 => [spec2, spec1] }, []]
|
31
|
+
Gem::SpecFetcher.any_instance.stubs(:available_specs).returns(response)
|
32
|
+
|
33
|
+
@cache = Command::GemIndexCache.new
|
34
|
+
@cache.download_and_cache_specs
|
35
|
+
versions_string =
|
36
|
+
Command::GemHelper.versions_string('cocoapods-plugins', @cache)
|
37
|
+
versions_string.should.include('0.2.0')
|
38
|
+
versions_string.should.include('0.1.0')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
# The CocoaPods namespace
|
4
|
+
#
|
5
|
+
module CLAide
|
6
|
+
describe Command::GemIndexCache do
|
7
|
+
before do
|
8
|
+
@cache = Command::GemIndexCache.new
|
9
|
+
UI_OUT.reopen
|
10
|
+
end
|
11
|
+
|
12
|
+
after do
|
13
|
+
mocha_teardown
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'notifies the user that it is downloading the spec index' do
|
17
|
+
response = [{}, []]
|
18
|
+
Gem::SpecFetcher.any_instance.stubs(:available_specs).returns(response)
|
19
|
+
|
20
|
+
@cache.download_and_cache_specs
|
21
|
+
out = UI_OUT.string
|
22
|
+
out.should.include('Downloading Rubygem specification index...')
|
23
|
+
out.should.not.include('Error downloading Rubygem specification')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'notifies the user when getting the spec index fails' do
|
27
|
+
error = Gem::RemoteFetcher::FetchError.new('no host', 'bad url')
|
28
|
+
wrapper_error = stub(:error => error)
|
29
|
+
response = [[], [wrapper_error]]
|
30
|
+
Gem::SpecFetcher.any_instance.stubs(:available_specs).returns(response)
|
31
|
+
|
32
|
+
@cache.download_and_cache_specs
|
33
|
+
@cache.specs.should.be.empty?
|
34
|
+
UI_OUT.string.should.include('Downloading Rubygem specification index...')
|
35
|
+
UI_OUT.string.should.include('Error downloading Rubygem specification')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|