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,113 @@
1
+ module Rip
2
+ module Commands
3
+ extend self
4
+
5
+ def invoke(args)
6
+ command, options, args = parse_args(args)
7
+
8
+ if command.nil? || command == ''
9
+ command = :help
10
+ end
11
+
12
+ command = find_command(command)
13
+
14
+ begin
15
+ send(command, options, *args)
16
+ rescue => e
17
+ if options[:error]
18
+ raise e
19
+ else
20
+ ui.puts "rip: #{command} failed"
21
+ ui.puts "-> #{e.message}"
22
+ end
23
+ end
24
+ end
25
+
26
+ def public_instance_methods
27
+ super - %w( invoke public_instance_methods )
28
+ end
29
+
30
+ private
31
+ # tasty dsl for adding help text
32
+
33
+ def o(usage)
34
+ @usage ||= {}
35
+ @next_usage = usage
36
+ end
37
+
38
+ def x(help)
39
+ @help ||= {}
40
+ @next_help ||= []
41
+ @next_help.push help
42
+ end
43
+
44
+ def method_added(method)
45
+ @help[method.to_s] = @next_help if @next_help
46
+ @usage[method.to_s] = @next_usage if @next_usage
47
+ @next_help = nil
48
+ @next_usage = nil
49
+ end
50
+
51
+ def ui
52
+ Rip.ui
53
+ end
54
+
55
+ def manager(env = nil)
56
+ @manager ||= PackageManager.new(env)
57
+ end
58
+
59
+ def find_command(command)
60
+ matches = public_instance_methods.select do |method|
61
+ method =~ /^#{command}/
62
+ end
63
+
64
+ if matches.size == 0
65
+ :help
66
+ elsif matches.size == 1
67
+ matches.first
68
+ else
69
+ ui.abort "rip: which command did you mean? #{matches.join(' or ')}"
70
+ end
71
+ end
72
+
73
+ def parse_args(args)
74
+ command = args.shift
75
+ options = args.select { |piece| piece =~ /^-/ }
76
+ args -= options
77
+ options = options.inject({}) do |hash, flag|
78
+ key, value = flag.split('=')
79
+ hash[key.sub(/^--?/,'').intern] = value.nil? ? true : value
80
+ hash
81
+ end
82
+
83
+ [command, options, args]
84
+ end
85
+ end
86
+ end
87
+
88
+
89
+ #
90
+ # rip plugin commands
91
+ #
92
+
93
+ # load ~/.rip/rip-commands/*.rb
94
+ if File.exists? dir = File.join(Rip.dir, 'rip-commands')
95
+ Dir[dir + '/*.rb'].each do |file|
96
+ require file
97
+ end
98
+ end
99
+
100
+ # load lib/rip/commands/*.rb from the active ripenv
101
+ if File.exists? dir = File.join(Rip::Env.active_dir, 'lib', 'rip', 'commands')
102
+ Dir[dir + '/*.rb'].each do |file|
103
+ require file
104
+ end
105
+ end
106
+
107
+
108
+ # load lib/rip/commands/*.rb from rip itself
109
+ if File.exists? dir = File.join(File.dirname(__FILE__), 'commands')
110
+ Dir[dir + '/*.rb'].each do |file|
111
+ require file
112
+ end
113
+ end
@@ -0,0 +1,23 @@
1
+ # file: lib/rip/commands/build.rb
2
+ #
3
+ # rip build
4
+ # Builds Ruby extensions for installed packages
5
+
6
+ module Rip
7
+ module Commands
8
+ def build(options={}, *packages)
9
+ packages.each do |package_name|
10
+ ui.puts "rip: building package: #{package_name}"
11
+ package = manager.package(package_name)
12
+
13
+ Dir["#{package.cache_path}/**/extconf.rb"].each do |build_file|
14
+ build_dir = File.dirname(build_file)
15
+ Dir.chdir(build_dir) {
16
+ system "ruby extconf.rb"
17
+ system "make install RUBYARCHDIR=#{manager.dir}/lib"
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,82 @@
1
+ module Rip
2
+ module Commands
3
+ x 'Display libraries installed in the current ripenv.'
4
+ def list(*args)
5
+ ui.puts 'ripenv: ' + Rip::Env.active, ''
6
+ if manager.packages.any?
7
+ ui.puts manager.packages
8
+ else
9
+ ui.puts "nothing installed"
10
+ end
11
+ end
12
+
13
+ def help(options = {}, command = nil, *args)
14
+ command = command.to_s
15
+ if !command.empty? && respond_to?(command)
16
+ ui.puts "Usage: %s" % (@usage[command] || "rip #{command.downcase}")
17
+ if @help[command]
18
+ ui.puts
19
+ ui.puts(*@help[command])
20
+ end
21
+ else
22
+ show_general_help
23
+ end
24
+ end
25
+
26
+ o 'rip env COMMAND'
27
+ x 'Commands for managing your ripenvs.'
28
+ x 'Type rip env to see valid options.'
29
+ def env(options = {}, command = nil, *args)
30
+ if command && Rip::Env.respond_to?(command)
31
+ ui.puts 'ripenv: ' + Rip::Env.call(command, *args).to_s
32
+ else
33
+ show_help :env, Rip::Env.commands
34
+ ui.puts '', "current ripenv: #{Rip::Env.active}"
35
+ end
36
+ end
37
+
38
+ x 'Outputs all installed libraries (and their versions) for the active env.'
39
+ x 'Can be saved as a .rip and installed by other rip users with:'
40
+ x ' rip install file.rip'
41
+ def freeze(options = {}, env = nil, *args)
42
+ manager(env).packages.each do |package|
43
+ ui.puts "#{package.source} #{package.version}"
44
+ end
45
+ end
46
+
47
+ x 'Prints the current version and exits.'
48
+ def version(options = {}, *args)
49
+ ui.puts "Rip #{Rip::Version}"
50
+ end
51
+ alias_method "-v", :version
52
+ alias_method "--version", :version
53
+
54
+ private
55
+ def show_help(command, commands)
56
+ subcommand = command.to_s.empty? ? nil : "#{command} "
57
+ ui.puts "Usage: rip #{subcommand}COMMAND [options]", ""
58
+ ui.puts "Commands available:"
59
+
60
+ commands.each do |method|
61
+ ui.puts " #{method}"
62
+ end
63
+ end
64
+
65
+ def show_general_help
66
+ commands = public_instance_methods.reject do |method|
67
+ method =~ /-/ || %w( help version ).include?(method)
68
+ end
69
+
70
+ show_help nil, commands
71
+
72
+ ui.puts
73
+ ui.puts "For more information on a a command use:"
74
+ ui.puts " rip help COMMAND"
75
+ ui.puts
76
+
77
+ ui.puts "Options: "
78
+ ui.puts " -h, --help show this help message and exit"
79
+ ui.puts " -v, --version show the current version and exit"
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,37 @@
1
+ module Rip
2
+ module Commands
3
+ x 'Checks that your rip installation is valid.'
4
+ def check(*args)
5
+ Setup.check_installation
6
+ ui.puts "All systems go."
7
+ rescue => e
8
+ ui.abort "Installation failed: #{e.message}"
9
+ end
10
+
11
+ o 'rip install SOURCE [options]'
12
+ x 'Installs a package from SOURCE.'
13
+ x '-f forces installation (overwrites existing)'
14
+ def install(options = {}, source = nil, version = nil, *args)
15
+ if source.to_s.empty?
16
+ ui.abort "Please tell me what to install."
17
+ end
18
+
19
+ package = Rip::Package.for(source, version)
20
+
21
+ if !package
22
+ ui.abort "I don't know how to install #{source}"
23
+ end
24
+
25
+ if options[:f]
26
+ Installer.new.uninstall(package) if package.installed?
27
+ Installer.new.install(package)
28
+ elsif package.installed?
29
+ ui.puts "#{package} already installed"
30
+ else
31
+ installer = Installer.new
32
+ installer.install(package)
33
+ # puts "#{installer.installed.size.to_i} packages installed"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,43 @@
1
+ module Rip
2
+ module Commands
3
+ o 'rip uninstall PACKAGE [options]'
4
+ x 'Uninstalls a single Rip package.'
5
+ x '-y removes the package no matter what.'
6
+ x '-d removes the package and its dependents.'
7
+ def uninstall(options = {}, name = nil, *args)
8
+ if name.to_s.empty?
9
+ ui.abort "Please tell me what to uninstall."
10
+ end
11
+
12
+ force = options[:y] || options[:d]
13
+ package = manager.package(name)
14
+
15
+ if !package
16
+ ui.abort "#{name} isn't installed."
17
+ end
18
+
19
+ dependents = manager.packages_that_depend_on(name)
20
+
21
+ if dependents.any? && !force
22
+ ui.puts "You have requested to uninstall the package:"
23
+ ui.puts " #{package}"
24
+ ui.puts
25
+ ui.puts "The following packages depend on #{name}:"
26
+
27
+ dependents.each do |dependent|
28
+ ui.puts " #{dependent}"
29
+ end
30
+
31
+ ui.puts
32
+ ui.puts "If you remove this package one or more dependencies will not be met."
33
+ ui.puts "Pass -y if you really want to remove #{name}"
34
+ ui.abort "Pass -d if you want to remove #{name} and its dependents."
35
+ end
36
+
37
+ if force || dependents.empty?
38
+ Installer.new.uninstall(package, options[:d])
39
+ ui.puts "Successfully uninstalled #{package}"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,128 @@
1
+ module Rip
2
+ module Env
3
+ extend self
4
+
5
+ def commands
6
+ instance_methods - %w( call active_dir commands ui )
7
+ end
8
+
9
+ def create(env)
10
+ dir = File.join(Rip.dir, env)
11
+
12
+ if env.strip.empty?
13
+ return "must give a ripenv to create"
14
+ end
15
+
16
+ if File.exists? dir
17
+ "#{env} exists"
18
+ else
19
+ FileUtils.mkdir_p File.join(dir, 'bin')
20
+ FileUtils.mkdir_p File.join(dir, 'lib')
21
+
22
+ use env
23
+ "created #{env}"
24
+ end
25
+ end
26
+
27
+ def use(env)
28
+ if env.strip.empty?
29
+ return "must give a ripenv to use"
30
+ end
31
+
32
+ if !File.exists?(target = File.join(Rip.dir, env))
33
+ return "#{env} doesn't exist"
34
+ end
35
+
36
+ begin
37
+ FileUtils.rm active_dir
38
+ rescue Errno::ENOENT
39
+ end
40
+ FileUtils.ln_s(target, active_dir)
41
+
42
+ "using #{env}"
43
+ end
44
+
45
+ def delete(env)
46
+ if active == env
47
+ return "can't delete active environment"
48
+ end
49
+
50
+ if env.strip.empty?
51
+ return "must give a ripenv to delete"
52
+ end
53
+
54
+ if File.exists?(target = File.join(Rip.dir, env))
55
+ FileUtils.rm_rf target
56
+ "deleted #{env}"
57
+ else
58
+ "can't find #{env}"
59
+ end
60
+ end
61
+
62
+ def list(env = nil)
63
+ envs = Dir.glob(File.join(Rip.dir, '*')).map do |env|
64
+ env.split('/').last
65
+ end
66
+
67
+ envs.reject! { |env| env =~ /^(rip-|active)/ }
68
+
69
+ if envs.empty?
70
+ "none. make one with `rip env create <env>`"
71
+ else
72
+ envs.join(' ')
73
+ end
74
+ end
75
+
76
+ def active
77
+ active = File.readlink(active_dir)
78
+ active.split('/').last
79
+ end
80
+
81
+ def copy(new)
82
+ if new.strip.empty?
83
+ return "must give a ripenv to copy to"
84
+ end
85
+
86
+ dest = File.join(Rip.dir, new)
87
+ src = Rip::Env.active_dir
88
+ env = Rip::Env.active
89
+
90
+ if File.exists?(dest)
91
+ return "#{new} exists"
92
+ end
93
+
94
+ FileUtils.cp_r src, dest
95
+
96
+ if File.exists? ripfile = File.join(dest, "#{env}.ripenv")
97
+ FileUtils.cp ripfile, File.join(dest, "#{new}.ripenv")
98
+ end
99
+
100
+ use new
101
+ "cloned #{env} to #{new}"
102
+ end
103
+
104
+ # for being lazy about what we have vs what we want.
105
+ # enables javascript-style method calling where
106
+ # the number of arguments doesn't need to match
107
+ # the arity
108
+ def call(meth, *args)
109
+ arity = method(meth).arity.abs
110
+
111
+ if arity == args.size
112
+ send(meth, *args)
113
+ elsif arity == 0
114
+ send(meth)
115
+ else
116
+ send(meth, args[0, arity])
117
+ end
118
+ end
119
+
120
+ def active_dir
121
+ File.join(Rip.dir, 'active')
122
+ end
123
+
124
+ def ui
125
+ Rip.ui
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,130 @@
1
+ module Rip
2
+ class Installer
3
+ include Memoize
4
+
5
+ attr_reader :installed
6
+ def initialize
7
+ @installed = {}
8
+ @uninstalled = {}
9
+ end
10
+
11
+ memoize :manager
12
+ def manager
13
+ PackageManager.new
14
+ end
15
+
16
+ def install(package, parent = nil)
17
+ if !package.exists?
18
+ error = package.name
19
+ error += " requested by #{parent} but" if parent
20
+ error += " not found at #{package.source}"
21
+ ui.abort error
22
+ end
23
+
24
+ Dir.chdir File.join(Rip.dir, 'rip-packages') do
25
+ begin
26
+ installed = @installed[package.name] || package.installed?
27
+
28
+ manager.add_package(package, parent) unless package.meta_package?
29
+
30
+ return if installed
31
+ @installed[package.name] = package
32
+
33
+ if !package.version
34
+ ui.abort "can't install #{package} - it has no version"
35
+ end
36
+
37
+ package.fetch
38
+ package.unpack
39
+ install_dependencies(package)
40
+ run_install_hook(package)
41
+ copy_files(package)
42
+ cleanup(package)
43
+ ui.puts "Successfully installed #{package}" unless package.meta_package?
44
+
45
+ rescue VersionConflict => e
46
+ ui.puts e.message
47
+ rollback
48
+ ui.abort "installation failed"
49
+
50
+ rescue => e
51
+ rollback
52
+ raise e
53
+ end
54
+ end
55
+ end
56
+
57
+ def install_dependencies(package)
58
+ package.dependencies.each do |dependency|
59
+ install(dependency, package)
60
+ end
61
+ end
62
+
63
+ def run_install_hook(package)
64
+ return unless File.exists? File.join(package.cache_path, 'Rakefile')
65
+
66
+ Dir.chdir package.cache_path do
67
+ ui.puts "running install hook for #{package.name}"
68
+ system "rake -s rip:install >& /dev/null"
69
+ end
70
+ end
71
+
72
+ def copy_files(package)
73
+ package_lib = File.join(package.cache_path, 'lib')
74
+ package_bin = File.join(package.cache_path, 'bin')
75
+
76
+ dest = Rip::Env.active_dir
77
+ dest_lib = File.join(dest, 'lib')
78
+ dest_bin = File.join(dest, 'bin')
79
+
80
+ if File.exists? package_lib
81
+ FileUtils.cp_r package_lib + '/.', dest_lib
82
+ end
83
+
84
+ if File.exists? package_bin
85
+ FileUtils.cp_r package_bin + '/.', dest_bin
86
+ end
87
+ end
88
+
89
+ def cleanup(package)
90
+ FileUtils.rm_rf package.cache_path unless package.cached?
91
+ end
92
+
93
+ def rollback
94
+ @installed.values.each do |package|
95
+ uninstall(package)
96
+ cleanup(package)
97
+ end
98
+ end
99
+
100
+ def uninstall(package, remove_dependencies = false)
101
+ packages = [package]
102
+
103
+ if remove_dependencies
104
+ packages.concat manager.packages_that_depend_on(package.name)
105
+ end
106
+
107
+ Dir.chdir Rip::Env.active_dir do
108
+ packages.each do |package|
109
+ begin
110
+ next if @uninstalled[package.name]
111
+ @uninstalled[package.name] = true
112
+
113
+ package.files.each do |file|
114
+ FileUtils.rm_rf file
115
+ end
116
+
117
+ manager.remove_package(package)
118
+ rescue => e
119
+ ui.puts e.message
120
+ next
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ def ui
127
+ Rip.ui
128
+ end
129
+ end
130
+ end