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