claide-plugins 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +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
|