rapt 0.1.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.
- data/CHANGELOG +4 -0
- data/MIT-LICENSE +21 -0
- data/README +4 -0
- data/Rakefile +74 -0
- data/bin/rapt +6 -0
- data/lib/commands/plugin.rb +65 -0
- data/lib/commands/plugin/commands.rb +486 -0
- data/lib/commands/plugin/plugin.rb +148 -0
- data/lib/commands/plugin/rails_environment.rb +87 -0
- data/lib/commands/plugin/recursive_http_fetcher.rb +65 -0
- data/lib/commands/plugin/repositories.rb +128 -0
- data/lib/commands/plugin/repository.rb +34 -0
- data/test/commands/plugin/plugin_test.rb +58 -0
- data/test/commands/plugin/repository_test.rb +20 -0
- data/test/commands/plugin_test.rb +75 -0
- data/test/mocks/rails_environment.rb +5 -0
- data/test/sandbox/rails_app/config/boot.rb +44 -0
- data/test/sandbox/rails_app/config/environment.rb +0 -0
- data/test/sandbox/rails_app/script/plugin +3 -0
- metadata +75 -0
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
class Plugin
|
4
|
+
|
5
|
+
attr_reader :name, :uri
|
6
|
+
|
7
|
+
def initialize(uri, name=nil)
|
8
|
+
@uri = uri
|
9
|
+
guess_name(uri)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.find(name)
|
13
|
+
name =~ /\// ? new(name) : Repositories.instance.find_plugin(name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
"#{@name.ljust(30)}#{@uri}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def svn_url?
|
21
|
+
@uri =~ /svn(?:\+ssh)?:\/\/*/
|
22
|
+
end
|
23
|
+
|
24
|
+
def installed?
|
25
|
+
File.directory?("#{rails_env.root}/vendor/plugins/#{name}") \
|
26
|
+
or rails_env.externals.detect{ |name, repo| self.uri == repo }
|
27
|
+
end
|
28
|
+
|
29
|
+
def install(method=nil, options = {})
|
30
|
+
method ||= rails_env.best_install_method?
|
31
|
+
method = :export if method == :http and svn_url?
|
32
|
+
|
33
|
+
uninstall if installed? and options[:force]
|
34
|
+
|
35
|
+
unless installed?
|
36
|
+
send("install_using_#{method}", options)
|
37
|
+
run_install_hook
|
38
|
+
else
|
39
|
+
puts "already installed: #{name} (#{uri}). pass --force to reinstall"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def uninstall
|
44
|
+
path = "#{rails_env.root}/vendor/plugins/#{name}"
|
45
|
+
if File.directory?(path)
|
46
|
+
puts "Removing 'vendor/plugins/#{name}'" if $verbose
|
47
|
+
run_uninstall_hook
|
48
|
+
rm_r path
|
49
|
+
else
|
50
|
+
puts "Plugin doesn't exist: #{path}"
|
51
|
+
end
|
52
|
+
# clean up svn:externals
|
53
|
+
externals = rails_env.externals
|
54
|
+
externals.reject!{|n,u| name == n or name == u}
|
55
|
+
rails_env.externals = externals
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns a hash representing this plugin's metadata
|
59
|
+
def about(force_refresh=false)
|
60
|
+
# Look in cache first
|
61
|
+
unless force_refresh
|
62
|
+
if Repositories.instance.source_cache['plugins'].has_key?(name)
|
63
|
+
return Repositories.instance.source_cache['plugins'][name]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
# Get from original source
|
67
|
+
tmp = "#{rails_env.root}/_tmp_about.yml"
|
68
|
+
if svn_url?
|
69
|
+
cmd = %(svn export #{@uri}/about.yml "#{tmp}")
|
70
|
+
puts cmd if $verbose
|
71
|
+
system(cmd)
|
72
|
+
end
|
73
|
+
about_uri = svn_url? ? tmp : File.join(@uri, 'about.yml')
|
74
|
+
open(about_uri) do |stream|
|
75
|
+
about_hash = YAML.load(stream.read)
|
76
|
+
unless about_hash.is_a?(Hash) && !about_hash['plugin'].nil?
|
77
|
+
raise("#{name}'s about.yml wasn't valid YAML")
|
78
|
+
end
|
79
|
+
return about_hash
|
80
|
+
end
|
81
|
+
rescue
|
82
|
+
# Make yaml on the fly for this plugin.
|
83
|
+
# The 'plugin' field (uri to the resource) is the only required field.
|
84
|
+
{
|
85
|
+
'plugin' => uri
|
86
|
+
}
|
87
|
+
ensure
|
88
|
+
FileUtils.rm_rf tmp if svn_url?
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def run_install_hook
|
94
|
+
install_hook_file = "#{rails_env.root}/vendor/plugins/#{name}/install.rb"
|
95
|
+
load install_hook_file if File.exists? install_hook_file
|
96
|
+
end
|
97
|
+
|
98
|
+
def run_uninstall_hook
|
99
|
+
uninstall_hook_file = "#{rails_env.root}/vendor/plugins/#{name}/uninstall.rb"
|
100
|
+
load uninstall_hook_file if File.exists? uninstall_hook_file
|
101
|
+
end
|
102
|
+
|
103
|
+
def install_using_export(options = {})
|
104
|
+
svn_command :export, options
|
105
|
+
end
|
106
|
+
|
107
|
+
def install_using_checkout(options = {})
|
108
|
+
svn_command :checkout, options
|
109
|
+
end
|
110
|
+
|
111
|
+
def install_using_externals(options = {})
|
112
|
+
externals = rails_env.externals
|
113
|
+
externals.push([@name, uri])
|
114
|
+
rails_env.externals = externals
|
115
|
+
install_using_checkout(options)
|
116
|
+
end
|
117
|
+
|
118
|
+
def install_using_http(options = {})
|
119
|
+
root = rails_env.root
|
120
|
+
mkdir_p "#{root}/vendor/plugins"
|
121
|
+
Dir.chdir "#{root}/vendor/plugins"
|
122
|
+
puts "fetching from '#{uri}'" if $verbose
|
123
|
+
fetcher = RecursiveHTTPFetcher.new(uri)
|
124
|
+
fetcher.quiet = true if options[:quiet]
|
125
|
+
fetcher.fetch
|
126
|
+
end
|
127
|
+
|
128
|
+
def svn_command(cmd, options = {})
|
129
|
+
root = rails_env.root
|
130
|
+
mkdir_p "#{root}/vendor/plugins"
|
131
|
+
base_cmd = "svn #{cmd} #{uri} \"#{root}/vendor/plugins/#{name}\""
|
132
|
+
base_cmd += ' -q' if options[:quiet] and not $verbose
|
133
|
+
base_cmd += " -r #{options[:revision]}" if options[:revision]
|
134
|
+
puts base_cmd if $verbose
|
135
|
+
system(base_cmd)
|
136
|
+
end
|
137
|
+
|
138
|
+
def guess_name(url)
|
139
|
+
@name = File.basename(url)
|
140
|
+
if @name == 'trunk' || @name.empty?
|
141
|
+
@name = File.basename(File.dirname(url))
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def rails_env
|
146
|
+
@rails_env || RailsEnvironment.default
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
|
2
|
+
class RailsEnvironment
|
3
|
+
attr_reader :root
|
4
|
+
|
5
|
+
def initialize(dir)
|
6
|
+
@root = dir
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.find(dir=nil)
|
10
|
+
dir ||= pwd
|
11
|
+
while dir.length > 1
|
12
|
+
return new(dir) if File.exist?(File.join(dir, 'config', 'environment.rb'))
|
13
|
+
dir = File.dirname(dir)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.default
|
18
|
+
@default ||= find
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.default=(rails_env)
|
22
|
+
@default = rails_env
|
23
|
+
end
|
24
|
+
|
25
|
+
def install(name_uri_or_plugin)
|
26
|
+
if name_uri_or_plugin.is_a? String
|
27
|
+
if name_uri_or_plugin =~ /:\/\//
|
28
|
+
plugin = Plugin.new(name_uri_or_plugin)
|
29
|
+
else
|
30
|
+
plugin = Plugins[name_uri_or_plugin]
|
31
|
+
end
|
32
|
+
else
|
33
|
+
plugin = name_uri_or_plugin
|
34
|
+
end
|
35
|
+
unless plugin.nil?
|
36
|
+
plugin.install
|
37
|
+
else
|
38
|
+
puts "plugin not found: #{name_uri_or_plugin}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def use_svn?
|
43
|
+
require 'active_support/core_ext/kernel'
|
44
|
+
silence_stderr {`svn --version` rescue nil}
|
45
|
+
!$?.nil? && $?.success?
|
46
|
+
end
|
47
|
+
|
48
|
+
def use_externals?
|
49
|
+
use_svn? && File.directory?("#{root}/vendor/plugins/.svn")
|
50
|
+
end
|
51
|
+
|
52
|
+
def use_checkout?
|
53
|
+
# this is a bit of a guess. we assume that if the rails environment
|
54
|
+
# is under subversion then they probably want the plugin checked out
|
55
|
+
# instead of exported. This can be overridden on the command line
|
56
|
+
File.directory?("#{root}/.svn")
|
57
|
+
end
|
58
|
+
|
59
|
+
def best_install_method?
|
60
|
+
return :http unless use_svn?
|
61
|
+
case
|
62
|
+
when use_externals? then :externals
|
63
|
+
when use_checkout? then :checkout
|
64
|
+
else :export
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def externals
|
69
|
+
return [] unless use_externals?
|
70
|
+
ext = `svn propget svn:externals "#{root}/vendor/plugins"`
|
71
|
+
ext.reject{ |line| line.strip == '' }.map do |line|
|
72
|
+
line.strip.split(/\s+/, 2)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def externals=(items)
|
77
|
+
unless items.is_a? String
|
78
|
+
items = items.map{|name,uri| "#{name.ljust(29)} #{uri.chomp('/')}"}.join("\n")
|
79
|
+
end
|
80
|
+
Tempfile.open("svn-set-prop") do |file|
|
81
|
+
file.write(items)
|
82
|
+
file.flush
|
83
|
+
system("svn propset -q svn:externals -F \"#{file.path}\" \"#{root}/vendor/plugins\"")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
class RecursiveHTTPFetcher
|
4
|
+
attr_accessor :quiet
|
5
|
+
def initialize(urls_to_fetch, cwd = ".")
|
6
|
+
@cwd = cwd
|
7
|
+
@urls_to_fetch = urls_to_fetch.to_a
|
8
|
+
@quiet = false
|
9
|
+
end
|
10
|
+
|
11
|
+
def ls
|
12
|
+
@urls_to_fetch.collect do |url|
|
13
|
+
if url =~ /^svn:\/\/.*/
|
14
|
+
`svn ls #{url}`.split("\n").map {|entry| "/#{entry}"} rescue nil
|
15
|
+
else
|
16
|
+
open(url) do |stream|
|
17
|
+
links("", stream.read)
|
18
|
+
end rescue nil
|
19
|
+
end
|
20
|
+
end.flatten
|
21
|
+
end
|
22
|
+
|
23
|
+
def push_d(dir)
|
24
|
+
@cwd = File.join(@cwd, dir)
|
25
|
+
FileUtils.mkdir_p(@cwd)
|
26
|
+
end
|
27
|
+
|
28
|
+
def pop_d
|
29
|
+
@cwd = File.dirname(@cwd)
|
30
|
+
end
|
31
|
+
|
32
|
+
def links(base_url, contents)
|
33
|
+
links = []
|
34
|
+
contents.scan(/href\s*=\s*\"*[^\">]*/i) do |link|
|
35
|
+
link = link.sub(/href="/i, "")
|
36
|
+
next if link =~ /^http/i || link =~ /^\./
|
37
|
+
links << File.join(base_url, link)
|
38
|
+
end
|
39
|
+
links
|
40
|
+
end
|
41
|
+
|
42
|
+
def download(link)
|
43
|
+
puts "+ #{File.join(@cwd, File.basename(link))}" unless @quiet
|
44
|
+
open(link) do |stream|
|
45
|
+
File.open(File.join(@cwd, File.basename(link)), "wb") do |file|
|
46
|
+
file.write(stream.read)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def fetch(links = @urls_to_fetch)
|
52
|
+
links.each do |l|
|
53
|
+
(l =~ /\/$/ || links == @urls_to_fetch) ? fetch_dir(l) : download(l)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def fetch_dir(url)
|
58
|
+
push_d(File.basename(url))
|
59
|
+
open(url) do |stream|
|
60
|
+
contents = stream.read
|
61
|
+
fetch(links(url, contents))
|
62
|
+
end
|
63
|
+
pop_d
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class Repositories
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_accessor :source_cache
|
7
|
+
|
8
|
+
def initialize(cache_file = File.join(find_home, ".rails", "plugin_source_cache.yml"))
|
9
|
+
@cache_file = File.expand_path(cache_file)
|
10
|
+
load!
|
11
|
+
end
|
12
|
+
|
13
|
+
def each(&block)
|
14
|
+
@repositories.each(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(uri)
|
18
|
+
unless find { |repo| repo.uri == uri }
|
19
|
+
repository = Repository.new(uri)
|
20
|
+
refresh_repository!(repository)
|
21
|
+
@repositories.push(repository).last
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def remove(uri)
|
26
|
+
@repositories.reject!{|repo| repo.uri == uri}
|
27
|
+
end
|
28
|
+
|
29
|
+
def exist?(uri)
|
30
|
+
@repositories.detect{|repo| repo.uri == uri }
|
31
|
+
end
|
32
|
+
|
33
|
+
def all
|
34
|
+
@repositories
|
35
|
+
end
|
36
|
+
|
37
|
+
def find_plugin(name)
|
38
|
+
# Try to get plugin from cache first
|
39
|
+
if @source_cache['plugins'].has_key?(name) && @source_cache['plugins'][name].has_key?('plugin')
|
40
|
+
return Plugin.new(@source_cache['plugins'][name]['plugin'], name)
|
41
|
+
end
|
42
|
+
|
43
|
+
@repositories.each do |repo|
|
44
|
+
repo.each do |plugin|
|
45
|
+
return plugin if plugin.name == name
|
46
|
+
end
|
47
|
+
end
|
48
|
+
return nil
|
49
|
+
end
|
50
|
+
|
51
|
+
# List the known plugins from the sources cache.
|
52
|
+
def cached_plugins
|
53
|
+
@source_cache['plugins'].keys.sort.map do |plugin_name|
|
54
|
+
Plugin.new(@source_cache['plugins'][plugin_name]['plugin'], plugin_name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the number of known plugins in the cache.
|
59
|
+
def plugin_count
|
60
|
+
source_cache['plugins'].keys.length
|
61
|
+
rescue
|
62
|
+
0
|
63
|
+
end
|
64
|
+
|
65
|
+
# Gets the updated list of plugins available at each repository.
|
66
|
+
def refresh!
|
67
|
+
@repositories.each { |repo| refresh_repository!(repo) }
|
68
|
+
end
|
69
|
+
|
70
|
+
# Gets the list of plugins available at this repository.
|
71
|
+
def refresh_repository!(repository)
|
72
|
+
repository.plugins.each do |plugin|
|
73
|
+
if about_hash = plugin.about(true)
|
74
|
+
@source_cache['plugins'][plugin.name] = about_hash
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def load!
|
81
|
+
@source_cache = File.exist?(@cache_file) ? YAML.load(File.read(@cache_file)) : defaults
|
82
|
+
@source_cache = defaults if @source_cache.keys.nil?
|
83
|
+
@repositories = @source_cache['repositories'].map { |source| Repository.new(source.strip) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def save
|
87
|
+
mkdir_p(File.dirname(@cache_file)) unless File.exist?(File.dirname(@cache_file))
|
88
|
+
File.open(@cache_file, 'w') do |f|
|
89
|
+
@source_cache['repositories'] = @repositories.map {|r| r.uri }.uniq
|
90
|
+
f.write(@source_cache.to_yaml)
|
91
|
+
f.write("\n")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def defaults
|
96
|
+
{
|
97
|
+
'repositories' => ['http://dev.rubyonrails.com/svn/rails/plugins/'],
|
98
|
+
'plugins' => {}
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def find_home
|
103
|
+
['HOME', 'USERPROFILE'].each do |homekey|
|
104
|
+
return ENV[homekey] if ENV[homekey]
|
105
|
+
end
|
106
|
+
if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
|
107
|
+
return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}"
|
108
|
+
end
|
109
|
+
begin
|
110
|
+
File.expand_path("~")
|
111
|
+
rescue StandardError => ex
|
112
|
+
if File::ALT_SEPARATOR
|
113
|
+
"C:/"
|
114
|
+
else
|
115
|
+
"/"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.instance
|
121
|
+
# TODO Use test cache file if $testing
|
122
|
+
@instance ||= Repositories.new
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.each(&block)
|
126
|
+
self.instance.each(&block)
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
class Repository
|
3
|
+
include Enumerable
|
4
|
+
attr_reader :uri, :plugins
|
5
|
+
|
6
|
+
def initialize(uri)
|
7
|
+
@uri = uri.chomp('/') << "/"
|
8
|
+
@plugins = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def plugins
|
12
|
+
unless @plugins
|
13
|
+
if $verbose
|
14
|
+
puts "Discovering plugins in #{@uri}"
|
15
|
+
puts index
|
16
|
+
end
|
17
|
+
|
18
|
+
@plugins = index.reject{ |line| line !~ /\/$/ }
|
19
|
+
@plugins.map! { |name| Plugin.new(File.join(@uri, name), name) }
|
20
|
+
end
|
21
|
+
|
22
|
+
@plugins
|
23
|
+
end
|
24
|
+
|
25
|
+
def each(&block)
|
26
|
+
plugins.each(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def index
|
31
|
+
@index ||= RecursiveHTTPFetcher.new(@uri).ls
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|