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.
Files changed (72) hide show
  1. data/.gitignore +4 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +306 -0
  4. data/Rakefile +51 -0
  5. data/bin/rip +6 -0
  6. data/examples/debug.rb +13 -0
  7. data/examples/reverse.rb +21 -0
  8. data/ext/extconf.rb +11 -0
  9. data/lib/rip.rb +53 -0
  10. data/lib/rip/commands.rb +113 -0
  11. data/lib/rip/commands/build.rb +23 -0
  12. data/lib/rip/commands/core.rb +82 -0
  13. data/lib/rip/commands/install.rb +37 -0
  14. data/lib/rip/commands/uninstall.rb +43 -0
  15. data/lib/rip/env.rb +128 -0
  16. data/lib/rip/installer.rb +130 -0
  17. data/lib/rip/memoize.rb +111 -0
  18. data/lib/rip/package.rb +126 -0
  19. data/lib/rip/package_api.rb +94 -0
  20. data/lib/rip/package_manager.rb +175 -0
  21. data/lib/rip/packages/dir_package.rb +34 -0
  22. data/lib/rip/packages/file_package.rb +60 -0
  23. data/lib/rip/packages/gem_package.rb +44 -0
  24. data/lib/rip/packages/git_package.rb +62 -0
  25. data/lib/rip/packages/http_package.rb +46 -0
  26. data/lib/rip/packages/remote_gem_package.rb +64 -0
  27. data/lib/rip/packages/ripfile_package.rb +46 -0
  28. data/lib/rip/setup.rb +205 -0
  29. data/lib/rip/sh/git.rb +35 -0
  30. data/lib/rip/ui.rb +24 -0
  31. data/lib/rip/version.rb +9 -0
  32. data/setup.rb +27 -0
  33. data/test/commands_test.rb +15 -0
  34. data/test/dev.rip +2 -0
  35. data/test/dir_test.rb +25 -0
  36. data/test/env_test.rb +173 -0
  37. data/test/git_test.rb +36 -0
  38. data/test/mock_git.rb +51 -0
  39. data/test/repos/simple_c/dot_git/HEAD +1 -0
  40. data/test/repos/simple_c/dot_git/config +6 -0
  41. data/test/repos/simple_c/dot_git/description +1 -0
  42. data/test/repos/simple_c/dot_git/hooks/applypatch-msg.sample +15 -0
  43. data/test/repos/simple_c/dot_git/hooks/commit-msg.sample +24 -0
  44. data/test/repos/simple_c/dot_git/hooks/post-commit.sample +8 -0
  45. data/test/repos/simple_c/dot_git/hooks/post-receive.sample +15 -0
  46. data/test/repos/simple_c/dot_git/hooks/post-update.sample +8 -0
  47. data/test/repos/simple_c/dot_git/hooks/pre-applypatch.sample +14 -0
  48. data/test/repos/simple_c/dot_git/hooks/pre-commit.sample +18 -0
  49. data/test/repos/simple_c/dot_git/hooks/pre-rebase.sample +169 -0
  50. data/test/repos/simple_c/dot_git/hooks/prepare-commit-msg.sample +36 -0
  51. data/test/repos/simple_c/dot_git/hooks/update.sample +107 -0
  52. data/test/repos/simple_c/dot_git/index +0 -0
  53. data/test/repos/simple_c/dot_git/info/exclude +6 -0
  54. data/test/repos/simple_c/dot_git/logs/HEAD +1 -0
  55. data/test/repos/simple_c/dot_git/logs/refs/heads/master +1 -0
  56. data/test/repos/simple_c/dot_git/objects/2d/94227280db3ac66875f52592c6a736b4526084 +0 -0
  57. data/test/repos/simple_c/dot_git/objects/3f/1d6dacdedf75058e9edf23f48de03fa451f7ce +1 -0
  58. data/test/repos/simple_c/dot_git/objects/53/23e9a7ff897fe7bfc3357d9274775b67f9ade4 +0 -0
  59. data/test/repos/simple_c/dot_git/objects/55/31db58bd71148661c400dc48815bf06b366128 +0 -0
  60. data/test/repos/simple_c/dot_git/objects/d7/55c6f119520808609a8d79bac1a8dbe0c57424 +0 -0
  61. data/test/repos/simple_c/dot_git/objects/d7/58739ea968ac8e8fe0cbbf1f6451af47f04964 +0 -0
  62. data/test/repos/simple_c/dot_git/objects/e6/7b81a24f0ce4bff84c3599b5c9ba7093f554d8 +0 -0
  63. data/test/repos/simple_c/dot_git/objects/ec/be0a80dc841c16beb2c733bbdd320b45565d89 +0 -0
  64. data/test/repos/simple_c/dot_git/refs/heads/master +1 -0
  65. data/test/repos/simple_c/ext/simp/extconf.rb +6 -0
  66. data/test/repos/simple_c/ext/simp/simp.c +17 -0
  67. data/test/repos/simple_c/lib/simple_c.rb +1 -0
  68. data/test/repos/simple_d-1.2.3/lib/simple_d.rb +1 -0
  69. data/test/rip_test.rb +8 -0
  70. data/test/test_helper.rb +79 -0
  71. data/test/ui_test.rb +36 -0
  72. metadata +149 -0
@@ -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
@@ -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