rip 0.0.1
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/.gitignore +4 -0
- data/LICENSE +20 -0
- data/README.markdown +306 -0
- data/Rakefile +51 -0
- data/bin/rip +6 -0
- data/examples/debug.rb +13 -0
- data/examples/reverse.rb +21 -0
- data/ext/extconf.rb +11 -0
- data/lib/rip.rb +53 -0
- data/lib/rip/commands.rb +113 -0
- data/lib/rip/commands/build.rb +23 -0
- data/lib/rip/commands/core.rb +82 -0
- data/lib/rip/commands/install.rb +37 -0
- data/lib/rip/commands/uninstall.rb +43 -0
- data/lib/rip/env.rb +128 -0
- data/lib/rip/installer.rb +130 -0
- data/lib/rip/memoize.rb +111 -0
- data/lib/rip/package.rb +126 -0
- data/lib/rip/package_api.rb +94 -0
- data/lib/rip/package_manager.rb +175 -0
- data/lib/rip/packages/dir_package.rb +34 -0
- data/lib/rip/packages/file_package.rb +60 -0
- data/lib/rip/packages/gem_package.rb +44 -0
- data/lib/rip/packages/git_package.rb +62 -0
- data/lib/rip/packages/http_package.rb +46 -0
- data/lib/rip/packages/remote_gem_package.rb +64 -0
- data/lib/rip/packages/ripfile_package.rb +46 -0
- data/lib/rip/setup.rb +205 -0
- data/lib/rip/sh/git.rb +35 -0
- data/lib/rip/ui.rb +24 -0
- data/lib/rip/version.rb +9 -0
- data/setup.rb +27 -0
- data/test/commands_test.rb +15 -0
- data/test/dev.rip +2 -0
- data/test/dir_test.rb +25 -0
- data/test/env_test.rb +173 -0
- data/test/git_test.rb +36 -0
- data/test/mock_git.rb +51 -0
- data/test/repos/simple_c/dot_git/HEAD +1 -0
- data/test/repos/simple_c/dot_git/config +6 -0
- data/test/repos/simple_c/dot_git/description +1 -0
- data/test/repos/simple_c/dot_git/hooks/applypatch-msg.sample +15 -0
- data/test/repos/simple_c/dot_git/hooks/commit-msg.sample +24 -0
- data/test/repos/simple_c/dot_git/hooks/post-commit.sample +8 -0
- data/test/repos/simple_c/dot_git/hooks/post-receive.sample +15 -0
- data/test/repos/simple_c/dot_git/hooks/post-update.sample +8 -0
- data/test/repos/simple_c/dot_git/hooks/pre-applypatch.sample +14 -0
- data/test/repos/simple_c/dot_git/hooks/pre-commit.sample +18 -0
- data/test/repos/simple_c/dot_git/hooks/pre-rebase.sample +169 -0
- data/test/repos/simple_c/dot_git/hooks/prepare-commit-msg.sample +36 -0
- data/test/repos/simple_c/dot_git/hooks/update.sample +107 -0
- data/test/repos/simple_c/dot_git/index +0 -0
- data/test/repos/simple_c/dot_git/info/exclude +6 -0
- data/test/repos/simple_c/dot_git/logs/HEAD +1 -0
- data/test/repos/simple_c/dot_git/logs/refs/heads/master +1 -0
- data/test/repos/simple_c/dot_git/objects/2d/94227280db3ac66875f52592c6a736b4526084 +0 -0
- data/test/repos/simple_c/dot_git/objects/3f/1d6dacdedf75058e9edf23f48de03fa451f7ce +1 -0
- data/test/repos/simple_c/dot_git/objects/53/23e9a7ff897fe7bfc3357d9274775b67f9ade4 +0 -0
- data/test/repos/simple_c/dot_git/objects/55/31db58bd71148661c400dc48815bf06b366128 +0 -0
- data/test/repos/simple_c/dot_git/objects/d7/55c6f119520808609a8d79bac1a8dbe0c57424 +0 -0
- data/test/repos/simple_c/dot_git/objects/d7/58739ea968ac8e8fe0cbbf1f6451af47f04964 +0 -0
- data/test/repos/simple_c/dot_git/objects/e6/7b81a24f0ce4bff84c3599b5c9ba7093f554d8 +0 -0
- data/test/repos/simple_c/dot_git/objects/ec/be0a80dc841c16beb2c733bbdd320b45565d89 +0 -0
- data/test/repos/simple_c/dot_git/refs/heads/master +1 -0
- data/test/repos/simple_c/ext/simp/extconf.rb +6 -0
- data/test/repos/simple_c/ext/simp/simp.c +17 -0
- data/test/repos/simple_c/lib/simple_c.rb +1 -0
- data/test/repos/simple_d-1.2.3/lib/simple_d.rb +1 -0
- data/test/rip_test.rb +8 -0
- data/test/test_helper.rb +79 -0
- data/test/ui_test.rb +36 -0
- metadata +149 -0
data/lib/rip/memoize.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# you know, for kids
|
2
|
+
module Rip
|
3
|
+
module Memoize
|
4
|
+
def self.included(base)
|
5
|
+
base.extend self
|
6
|
+
end
|
7
|
+
|
8
|
+
def memoize(method)
|
9
|
+
@memoized ||= {}
|
10
|
+
@memoized[method] = true
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_added(method)
|
14
|
+
@memoized ||= {}
|
15
|
+
return unless @memoized.delete(method)
|
16
|
+
|
17
|
+
real_name = "__memoized_#{method}"
|
18
|
+
alias_method real_name, method
|
19
|
+
|
20
|
+
if self.instance_method(method).arity == 0
|
21
|
+
define_method method do
|
22
|
+
if instance_variable_defined? ivar = "@#{method}"
|
23
|
+
instance_variable_get ivar
|
24
|
+
else
|
25
|
+
instance_variable_set ivar, send(real_name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
else
|
29
|
+
define_method method do |*args|
|
30
|
+
@memoize_cache ||= {}
|
31
|
+
key = [method, args].hash
|
32
|
+
|
33
|
+
if @memoize_cache.has_key?(key)
|
34
|
+
@memoize_cache[key]
|
35
|
+
else
|
36
|
+
@memoize_cache[key] = send(real_name, *args)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if __FILE__ == $0
|
45
|
+
require 'test/unit'
|
46
|
+
|
47
|
+
class TestMemoize < Test::Unit::TestCase
|
48
|
+
class Encoder
|
49
|
+
include Rip::Memoize
|
50
|
+
|
51
|
+
attr_reader :encodes
|
52
|
+
def initialize
|
53
|
+
@encodes = 0
|
54
|
+
end
|
55
|
+
|
56
|
+
memoize :encode
|
57
|
+
def encode(name)
|
58
|
+
@encodes += 1
|
59
|
+
|
60
|
+
parts = name.split(//).map do |letter|
|
61
|
+
letter[0]
|
62
|
+
end
|
63
|
+
|
64
|
+
parts.join('/')
|
65
|
+
end
|
66
|
+
|
67
|
+
memoize :simple
|
68
|
+
def simple
|
69
|
+
@encodes += 1
|
70
|
+
1 + 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def setup
|
75
|
+
@obj = Encoder.new
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_properly_executes_methods
|
79
|
+
@obj.encode('chris')
|
80
|
+
assert_equal '99/104/114/105/115', @obj.encode('chris')
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_memoizes_method
|
84
|
+
@obj.encode('chris')
|
85
|
+
@obj.encode('chris')
|
86
|
+
@obj.encode('chris')
|
87
|
+
|
88
|
+
assert_equal 1, @obj.encodes
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_memoizes_multiple_methods
|
92
|
+
@obj.encode('chris')
|
93
|
+
@obj.encode('bob')
|
94
|
+
@obj.encode('chris')
|
95
|
+
@obj.encode('bob')
|
96
|
+
|
97
|
+
assert_equal 2, @obj.encodes
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_memoizes_argless_methods
|
101
|
+
@obj.simple
|
102
|
+
@obj.simple
|
103
|
+
assert_equal 1, @obj.encodes
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_uses_simple_ivar_for_argless_methods
|
107
|
+
@obj.simple
|
108
|
+
assert_equal 2, @obj.instance_variable_get(:@simple)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/rip/package.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
#
|
4
|
+
# Want to write your own package?
|
5
|
+
#
|
6
|
+
# Check Rip::PackageAPI for the methods you need.
|
7
|
+
#
|
8
|
+
|
9
|
+
module Rip
|
10
|
+
class Package
|
11
|
+
include PackageAPI, Memoize
|
12
|
+
|
13
|
+
attr_reader :source
|
14
|
+
def initialize(source, version = nil, files = nil)
|
15
|
+
@source = source.strip.chomp
|
16
|
+
@version = version
|
17
|
+
@files = files
|
18
|
+
end
|
19
|
+
|
20
|
+
@@patterns = {}
|
21
|
+
@@blocks = {}
|
22
|
+
|
23
|
+
def self.handles(*patterns, &block)
|
24
|
+
patterns.each do |pattern|
|
25
|
+
@@patterns[pattern] = self
|
26
|
+
end
|
27
|
+
|
28
|
+
@@blocks[self] = block if block
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.for(source, *args)
|
32
|
+
source = source.strip.chomp
|
33
|
+
|
34
|
+
handler = @@patterns.detect do |pattern, klass|
|
35
|
+
case pattern
|
36
|
+
when String
|
37
|
+
if pattern[0,1] == '.'
|
38
|
+
pattern = Regexp.escape(pattern)
|
39
|
+
source.match Regexp.new("#{pattern}$")
|
40
|
+
else
|
41
|
+
source.include? pattern
|
42
|
+
end
|
43
|
+
else
|
44
|
+
source.match(pattern)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
return handler[1].new(source, *args) if handler
|
49
|
+
|
50
|
+
handler = @@blocks.detect do |klass, block|
|
51
|
+
block.call(source)
|
52
|
+
end
|
53
|
+
|
54
|
+
return handler[0].new(source, *args) if handler
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
"#{name} (#{version})"
|
59
|
+
end
|
60
|
+
|
61
|
+
memoize :cache_name
|
62
|
+
def cache_name
|
63
|
+
name + '-' + Digest::MD5.hexdigest(@source)
|
64
|
+
end
|
65
|
+
|
66
|
+
memoize :cache_path
|
67
|
+
def cache_path
|
68
|
+
File.join(packages_path, cache_name)
|
69
|
+
end
|
70
|
+
|
71
|
+
memoize :packages_path
|
72
|
+
def packages_path
|
73
|
+
File.join(Rip.dir, 'rip-packages')
|
74
|
+
end
|
75
|
+
|
76
|
+
def installed?
|
77
|
+
graph = PackageManager.new
|
78
|
+
graph.installed?(name) && graph.package_version(name) == version
|
79
|
+
end
|
80
|
+
|
81
|
+
def fetch
|
82
|
+
return if @fetched
|
83
|
+
fetch!
|
84
|
+
@fetched = true
|
85
|
+
end
|
86
|
+
|
87
|
+
def unpack
|
88
|
+
return if @unpacked
|
89
|
+
unpack!
|
90
|
+
@unpacked = true
|
91
|
+
end
|
92
|
+
|
93
|
+
def files
|
94
|
+
@files ||= files!
|
95
|
+
end
|
96
|
+
|
97
|
+
def files!
|
98
|
+
fetch
|
99
|
+
unpack
|
100
|
+
|
101
|
+
Dir.chdir cache_path do
|
102
|
+
Dir['lib/**/*'] + Dir['bin/**/*']
|
103
|
+
end
|
104
|
+
end
|
105
|
+
attr_writer :files
|
106
|
+
|
107
|
+
def dependencies
|
108
|
+
@dependencies ||= dependencies!
|
109
|
+
end
|
110
|
+
|
111
|
+
def dependencies!
|
112
|
+
if File.exists? deps = File.join(cache_path, 'deps.rip')
|
113
|
+
File.readlines(deps).map do |line|
|
114
|
+
source, version, *extra = line.split(' ')
|
115
|
+
Package.for(source, version)
|
116
|
+
end
|
117
|
+
else
|
118
|
+
[]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def ui
|
123
|
+
Rip.ui
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
#
|
2
|
+
# When writing your own package, you'll want to override the
|
3
|
+
# methods provided by PackageAPI.
|
4
|
+
#
|
5
|
+
# The following Package methods may also be of interested to
|
6
|
+
# you:
|
7
|
+
#
|
8
|
+
# source - The path, URL, or name of the package's source
|
9
|
+
# cache_name - The name of the package's cache directory
|
10
|
+
# cache_path - The path to the package's cache directory
|
11
|
+
#
|
12
|
+
# You'll also want your package to listen for certain patterns
|
13
|
+
# in sources.
|
14
|
+
#
|
15
|
+
# For example, a GitPackage is needed when the source begins with
|
16
|
+
# "git://". To hook this up, we'd add the following to
|
17
|
+
# Rip::GitPackage:
|
18
|
+
#
|
19
|
+
# class Rip::GitPackage
|
20
|
+
# handles "git://"
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# The `handles` method can accept multiple parameters and
|
24
|
+
# regular expressions.
|
25
|
+
#
|
26
|
+
# It also accepts a block, which will be passed the source.
|
27
|
+
# If the block evaluates to true then that package type is used.
|
28
|
+
#
|
29
|
+
# For example:
|
30
|
+
#
|
31
|
+
# class Rip::LocalGitPackage
|
32
|
+
# handles do |source|
|
33
|
+
# File.exists? File.join(source, '.git')
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
|
38
|
+
module Rip
|
39
|
+
module PackageAPI
|
40
|
+
# The package's name
|
41
|
+
def name
|
42
|
+
source
|
43
|
+
end
|
44
|
+
|
45
|
+
# We weren't given a specific version, so figure
|
46
|
+
# out what the latest version is and return it
|
47
|
+
def version
|
48
|
+
"0.0.1"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Does this package's source exist?
|
52
|
+
def exists?
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
# Grab the package and stick it in our local cache,
|
57
|
+
# if it's not already there.
|
58
|
+
def fetch!
|
59
|
+
ui.puts "fetching #{name}..."
|
60
|
+
end
|
61
|
+
|
62
|
+
# Unpack the package we want into the cache.
|
63
|
+
def unpack!
|
64
|
+
ui.puts "unpacking #{self}..."
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# The following are more obscure hooks, not usually needed
|
69
|
+
# for authoring a package.
|
70
|
+
#
|
71
|
+
|
72
|
+
# Does this package simply install other packages?
|
73
|
+
# Usually not.
|
74
|
+
def meta_package?
|
75
|
+
false
|
76
|
+
end
|
77
|
+
|
78
|
+
# Should this package be cached in rip-packages?
|
79
|
+
# Usually so.
|
80
|
+
def cached?
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
# A list of installed files. Usually handled by Package
|
85
|
+
def files!
|
86
|
+
[]
|
87
|
+
end
|
88
|
+
|
89
|
+
# Packages we depend on. Usually handled by Package.
|
90
|
+
def dependencies!
|
91
|
+
[]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Rip
|
5
|
+
class VersionConflict < RuntimeError
|
6
|
+
def initialize(name, bad_version, requester, real_version, owners)
|
7
|
+
@name = name
|
8
|
+
@bad_version = bad_version
|
9
|
+
@requester = requester
|
10
|
+
@real_version = real_version
|
11
|
+
@owners = owners
|
12
|
+
end
|
13
|
+
|
14
|
+
def message
|
15
|
+
message = []
|
16
|
+
message << "version conflict!"
|
17
|
+
message << "#{@name} requested at #{@bad_version} by #{@requester}"
|
18
|
+
|
19
|
+
if @owners.size == 1
|
20
|
+
owners = @owners[0]
|
21
|
+
elsif @owners.size == 2
|
22
|
+
owners = "#{@owners[0]} and #{@owners[1]}"
|
23
|
+
else
|
24
|
+
owners = [ @owners[0...-1], "and #{@owners[-1]}" ].join(', ')
|
25
|
+
end
|
26
|
+
|
27
|
+
message << "#{@name} previously requested at #{@real_version} by #{owners}"
|
28
|
+
message.join("\n")
|
29
|
+
end
|
30
|
+
alias_method :to_s, :message
|
31
|
+
end
|
32
|
+
|
33
|
+
class PackageManager
|
34
|
+
attr_reader :dependencies, :dependents, :sources, :versions, :env
|
35
|
+
|
36
|
+
def initialize(env = nil)
|
37
|
+
@env = env || Rip::Env.active
|
38
|
+
load
|
39
|
+
|
40
|
+
# key is the package name, value is the current
|
41
|
+
# installed version
|
42
|
+
@versions ||= {}
|
43
|
+
|
44
|
+
# key is the package name, value is an array of
|
45
|
+
# libraries it depend on
|
46
|
+
@dependents ||= {}
|
47
|
+
|
48
|
+
# key is the package name, value is an array of
|
49
|
+
# libraries that depend on it
|
50
|
+
@dependencies ||= {}
|
51
|
+
|
52
|
+
# key is the package name, value is the source
|
53
|
+
@sources ||= {}
|
54
|
+
|
55
|
+
# key is the package name, value is the installed
|
56
|
+
# files
|
57
|
+
@files ||= {}
|
58
|
+
end
|
59
|
+
|
60
|
+
def inspect
|
61
|
+
"(#{self.class} dependencies=#{dependencies.inspect} dependents=#{dependents.inspect} sources=#{sources.inspect} versions=#{versions.inspect})"
|
62
|
+
end
|
63
|
+
|
64
|
+
def packages
|
65
|
+
@versions.keys.map { |name| package(name) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def package_names
|
69
|
+
@versions.keys
|
70
|
+
end
|
71
|
+
|
72
|
+
def package(name)
|
73
|
+
return unless @versions[name]
|
74
|
+
Package.for(@sources[name], @versions[name], @files[name])
|
75
|
+
end
|
76
|
+
|
77
|
+
def packages_that_depend_on(name)
|
78
|
+
(@dependents[name] || []).map { |name| package(name) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def files(name)
|
82
|
+
Array(@files[name])
|
83
|
+
end
|
84
|
+
|
85
|
+
def installed?(name)
|
86
|
+
@versions.has_key? name
|
87
|
+
end
|
88
|
+
|
89
|
+
def package_version(name)
|
90
|
+
@versions[name]
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_package(package, parent = nil)
|
94
|
+
name = package.name
|
95
|
+
version = package.version
|
96
|
+
|
97
|
+
if @versions.has_key?(name) && @versions[name] != version
|
98
|
+
raise VersionConflict.new(name, version, parent, @versions[name], @dependents[name].to_a)
|
99
|
+
end
|
100
|
+
|
101
|
+
if parent && !parent.meta_package?
|
102
|
+
@dependents[name] ||= Set.new
|
103
|
+
@dependents[name].add(parent.name)
|
104
|
+
@dependencies[parent.name] ||= Set.new
|
105
|
+
@dependencies[parent.name].add(name)
|
106
|
+
end
|
107
|
+
|
108
|
+
# already installed?
|
109
|
+
if @versions.has_key? name
|
110
|
+
false
|
111
|
+
else
|
112
|
+
@versions[name] = version
|
113
|
+
@sources[name] = package.source
|
114
|
+
@files[name] = package.files
|
115
|
+
true
|
116
|
+
end
|
117
|
+
ensure
|
118
|
+
save
|
119
|
+
end
|
120
|
+
|
121
|
+
def add_files(name, file_list = [])
|
122
|
+
@files[name] ||= []
|
123
|
+
@files[name].concat file_list
|
124
|
+
save
|
125
|
+
end
|
126
|
+
|
127
|
+
def remove_package(package)
|
128
|
+
name = package.name
|
129
|
+
|
130
|
+
Array(@dependencies[name]).each do |dep|
|
131
|
+
@dependents[dep].delete(name) if @dependents[dep].respond_to? :delete
|
132
|
+
end
|
133
|
+
|
134
|
+
@dependents.delete(name)
|
135
|
+
@dependencies.delete(name)
|
136
|
+
@versions.delete(name)
|
137
|
+
save
|
138
|
+
end
|
139
|
+
|
140
|
+
def path
|
141
|
+
File.join(dir, "#{@env}.ripenv")
|
142
|
+
end
|
143
|
+
|
144
|
+
def dir
|
145
|
+
File.join(Rip.dir, @env)
|
146
|
+
end
|
147
|
+
|
148
|
+
def save
|
149
|
+
File.open(path, 'w') do |f|
|
150
|
+
f.puts zip(marshal_payload)
|
151
|
+
f.flush
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def load
|
156
|
+
marshal_read unzip(File.read(path)) if File.exists? path
|
157
|
+
end
|
158
|
+
|
159
|
+
def zip(data)
|
160
|
+
Zlib::Deflate.deflate(data)
|
161
|
+
end
|
162
|
+
|
163
|
+
def unzip(data)
|
164
|
+
Zlib::Inflate.inflate(data)
|
165
|
+
end
|
166
|
+
|
167
|
+
def marshal_payload
|
168
|
+
Marshal.dump [ @versions, @dependents, @dependencies, @sources, @files ]
|
169
|
+
end
|
170
|
+
|
171
|
+
def marshal_read(data)
|
172
|
+
@versions, @dependents, @dependencies, @sources, @files = Marshal.load(data)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|