├── .gems ├── test ├── .gems └── dep.rb ├── rakefile ├── dep.gemspec ├── README.md ├── README └── bin └── dep /.gems: -------------------------------------------------------------------------------- 1 | cutest -v 1.2.0 2 | override -v 0.0.10 3 | -------------------------------------------------------------------------------- /test/.gems: -------------------------------------------------------------------------------- 1 | ohm-contrib -v 1.0.rc1 2 | cutest -v 1.2.0 3 | -------------------------------------------------------------------------------- /rakefile: -------------------------------------------------------------------------------- 1 | task :default => :test 2 | 3 | task :test do 4 | require "cutest" 5 | 6 | Cutest.run(Dir["test/*.rb"]) 7 | end 8 | -------------------------------------------------------------------------------- /dep.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "dep" 3 | s.version = "1.1.0" 4 | s.summary = "Dependencies manager" 5 | s.description = "Specify your project's dependencies in one file." 6 | s.authors = ["Cyril David", "Michel Martens"] 7 | s.email = ["cyx.ucron@gmail.com", "soveran@gmail.com"] 8 | s.homepage = "http://cyx.github.com/dep" 9 | s.files = ["bin/dep", "test/dep.rb"] 10 | 11 | s.executables.push("dep") 12 | end 13 | -------------------------------------------------------------------------------- /test/dep.rb: -------------------------------------------------------------------------------- 1 | require "cutest" 2 | require "override" 3 | 4 | load File.expand_path("../../bin/dep", __FILE__) 5 | 6 | class Cutest::Scope 7 | include Override 8 | end 9 | 10 | # Dep::Lib 11 | scope do 12 | test "parsing" do 13 | lib = Dep::Lib["ohm-contrib -v 1.0.rc1"] 14 | 15 | assert_equal "ohm-contrib", lib.name 16 | assert_equal "1.0.rc1", lib.version 17 | end 18 | 19 | test "availability of existing gem" do 20 | lib = Dep::Lib.new("cutest", "1.2.0") 21 | assert lib.available? 22 | end 23 | 24 | test "non-availability of missing gem" do 25 | lib = Dep::Lib.new("rails", "3.0") 26 | assert ! lib.available? 27 | end 28 | 29 | test "to_s" do 30 | lib = Dep::Lib.new("cutest", "1.1.3") 31 | assert_equal "cutest -v 1.1.3", lib.to_s 32 | end 33 | end 34 | 35 | # Dep::List 36 | scope do 37 | setup do 38 | Dep::List.new(File.expand_path(".gems", File.dirname(__FILE__))) 39 | end 40 | 41 | test do |list| 42 | lib1 = Dep::Lib.new("ohm-contrib", "1.0.rc1") 43 | lib2 = Dep::Lib.new("cutest", "1.2.0") 44 | 45 | assert list.libraries.include?(lib1) 46 | assert list.libraries.include?(lib2) 47 | 48 | assert_equal 1, list.missing_libraries.size 49 | assert list.missing_libraries.include?(lib1) 50 | end 51 | 52 | test do |list| 53 | list.add(Dep::Lib.new("cutest", "2.0")) 54 | 55 | assert ! list.libraries.include?(Dep::Lib.new("cutest", "1.1.3")) 56 | assert list.libraries.include?(Dep::Lib.new("cutest", "2.0")) 57 | end 58 | end 59 | 60 | # Dep::CLI 61 | scope do 62 | setup do 63 | list = Dep::List.new("/dev/null") 64 | 65 | list.add(Dep::Lib.new("foo", "2.0")) 66 | list.add(Dep::Lib.new("bar", "1.1")) 67 | 68 | commands = [] 69 | 70 | cli = Dep::CLI.new 71 | cli.list = list 72 | 73 | override(cli, run: -> args { commands << args }) 74 | 75 | [cli, commands] 76 | end 77 | 78 | test "install" do |cli, commands| 79 | cli.install 80 | 81 | assert_equal ["gem install foo:2.0 bar:1.1"], commands 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DEP(1) -- basic dependency tracking 2 | =================================== 3 | 4 | ## SYNOPSIS 5 | 6 | dep 7 | dep add libname [--pre] 8 | dep rm libname 9 | dep install 10 | 11 | ## DESCRIPTION 12 | 13 | * `dep` : 14 | Checks that all dependencies are met. 15 | 16 | * `dep add [gemname]` : 17 | Fetches the latest version of `gemname` 18 | and automatically adds it to your .gems file. 19 | 20 | * `dep rm` : 21 | Removes the corresponding entry in your .gems file. 22 | 23 | * `dep install` : 24 | Installs all the missing dependencies for you. An important 25 | point here is that it simply does a `gem install` for each 26 | dependency you have. Dep assumes that you use some form of 27 | sandboxing like gs, RVM or rbenv-gemset. 28 | 29 | 30 | ## INSTALLATION 31 | 32 | $ gem install dep 33 | 34 | ## HISTORY 35 | 36 | dep is actually more of a workflow than a tool. If you think about 37 | package managers and the problem of dependencies, you can summarize 38 | what you absolutely need from them in just two points: 39 | 40 | 1. When you build an application which relies on 3rd party libraries, 41 | it's best to explicitly declare the version numbers of these 42 | libraries. 43 | 2. You can either bundle the specific library version together with 44 | your application, or you can have a list of versions. 45 | 46 | The first approach is handled by vendoring the library. The second 47 | approach typically is done using Bundler. But why do you need such 48 | a complicated tool when all you need is simply listing version numbers? 49 | 50 | We dissected what we were doing and eventually reached the following 51 | workflow: 52 | 53 | 1. We maintain a .gems file for every application which lists the 54 | libraries and the version numbers. 55 | 2. We omit dependencies of dependencies in that file, the reason being 56 | is that that should already be handled by the package manager 57 | (typically rubygems). 58 | 3. Whenever we add a new library, we add the latest version. 59 | 4. When we pull the latest changes, we want to be able to rapidly 60 | check if the dependencies we have is up to date and matches what 61 | we just pulled. 62 | 63 | So after doing this workflow manually for a while, we decided to 64 | build the simplest tool to aid us with our workflow. 65 | 66 | - The first point is handled implicitly by dep. You can also specify 67 | a different file by doing dep -f. 68 | - The second point is more of an implementation detail. We thought about 69 | doing dependencies, but then, why re-implement something that's already 70 | done for you by rubygems? 71 | - The third point (and also the one which is most inconvenient), is 72 | handled by dep add. 73 | 74 | The manual workflow for `dep add` would be: 75 | 76 | gem search -r "^ohm$" [--pre] # check and remember the version number 77 | echo "ohm -v X.x.x" >> .gems 78 | 79 | If you try doing that repeatedly, it will quickly become cumbersome. 80 | 81 | The fourth and final point is handled by typing dep check or simply dep. 82 | Practically speaking it's just: 83 | 84 | git pull 85 | dep 86 | 87 | And that's it. The dep command typically happens in 0.2 seconds which 88 | is something we LOVE. 89 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | DEP(1) 2 | 3 | NAME 4 | dep -- Basic dependency tracking 5 | 6 | SYNOPSIS 7 | dep 8 | dep add libname [--pre] 9 | dep rm libname 10 | dep install 11 | 12 | DESCRIPTION 13 | dep 14 | Checks that all dependencies are met. 15 | 16 | dep add [gemname] 17 | Fetches the latest version of `gemname` 18 | and automatically adds it to your .gems file. 19 | 20 | rm 21 | Removes the corresponding entry in your .gems file. 22 | 23 | install 24 | Installs all the missing dependencies for you. An important 25 | point here is that it simply does a `gem install` for each 26 | dependency you have. Dep assumes that you use some form of 27 | sandboxing like gs, rbenv-gemset or RVM gemsets. 28 | 29 | 30 | INSTALLATION 31 | $ wget -qO- http://amakawa.org/sh/install.sh | sh 32 | 33 | # or 34 | 35 | $ gem install dep 36 | 37 | HISTORY 38 | dep is actually more of a workflow than a tool. If you think about 39 | package managers and the problem of dependencies, you can summarize 40 | what you absolutely need from them in just two points: 41 | 42 | 1. When you build an application which relies on 3rd party libraries, 43 | it's best to explicitly declare the version numbers of these 44 | libraries. 45 | 46 | 2. You can either bundle the specific library version together with 47 | your application, or you can have a list of versions. 48 | 49 | The first approach is handled by vendoring the library. The second 50 | approach typically is done using Bundler. But why do you need such 51 | a complicated tool when all you need is simply listing version numbers? 52 | 53 | We dissected what we were doing and eventually reached the following 54 | workflow: 55 | 56 | 1. We maintain a .gems file for every application which lists the 57 | libraries and the version numbers. 58 | 2. We omit dependencies of dependencies in that file, the reason being 59 | is that that should already be handled by the package manager 60 | (typically rubygems). 61 | 3. Whenever we add a new library, we add the latest version. 62 | 4. When we pull the latest changes, we want to be able to rapidly 63 | check if the dependencies we have is up to date and matches what 64 | we just pulled. 65 | 66 | So after doing this workflow manually for a while, we decided to 67 | build the simplest tool to aid us with our workflow. 68 | 69 | The first point is handled implicitly by dep. You can also specify 70 | a different file by doing dep -f. 71 | 72 | The second point is more of an implementation detail. We thought about 73 | doing dependencies, but then, why re-implement something that's already 74 | done for you by rubygems? 75 | 76 | The third point (and also the one which is most inconvenient), is 77 | handled by dep add. 78 | 79 | The manual workflow for that would be: 80 | 81 | gem search -r "^ohm$" [--pre] # check and remember the version number 82 | echo "ohm -v X.x.x" >> .gems 83 | 84 | If you try doing that repeatedly, it will quickly become cumbersome. 85 | 86 | The fourth and final point is handled by typing dep check or simply dep. 87 | Practically speaking it's just: 88 | 89 | git pull 90 | dep 91 | 92 | And that's it. The dep command typically happens in 0.2 seconds which 93 | is something we LOVE. 94 | -------------------------------------------------------------------------------- /bin/dep: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "fileutils" 4 | 5 | def die 6 | abort("error: dep --help for more info") 7 | end 8 | 9 | module Dep 10 | class List 11 | attr :path 12 | 13 | def initialize(path) 14 | @path = path 15 | end 16 | 17 | def add(lib) 18 | remove(lib) 19 | libraries.push(lib) 20 | end 21 | 22 | def remove(lib) 23 | libraries.delete_if { |e| e.name == lib.name } 24 | end 25 | 26 | def libraries 27 | @libraries ||= File.readlines(path).map { |line| Lib[line] } 28 | end 29 | 30 | def missing_libraries 31 | libraries.reject(&:available?) 32 | end 33 | 34 | def save 35 | File.open(path, "w") do |file| 36 | libraries.each do |lib| 37 | file.puts lib.to_s 38 | end 39 | end 40 | end 41 | end 42 | 43 | class Lib < Struct.new(:name, :version) 44 | def self.[](line) 45 | if line.strip =~ /^(\S+) -v (\S+)$/ 46 | return new($1, $2) 47 | else 48 | abort("Invalid requirement found: #{line}") 49 | end 50 | end 51 | 52 | def available? 53 | Gem::Specification.find_by_name(name, version) 54 | rescue Gem::LoadError 55 | return false 56 | end 57 | 58 | def to_s 59 | "#{name} -v #{version}" 60 | end 61 | 62 | def csv 63 | "#{name}:#{version}" 64 | end 65 | 66 | def ==(other) 67 | to_s == other.to_s 68 | end 69 | end 70 | 71 | class CLI 72 | attr_accessor :prerelease, :list, :file 73 | 74 | def add(name) 75 | dependency = Gem::Dependency.new(name) 76 | fetcher = Gem::SpecFetcher.fetcher 77 | 78 | if fetcher.respond_to?(:spec_for_dependency) 79 | dependency.prerelease = @prerelease 80 | res, _ = fetcher.spec_for_dependency(dependency) 81 | else 82 | res = fetcher.fetch(dependency, false, true, @prerelease) 83 | end 84 | 85 | abort("Unable to find #{name}") if res.empty? 86 | 87 | spec = res[-1][0] 88 | lib = Dep::Lib.new(spec.name, spec.version) 89 | 90 | @list.add(lib) 91 | @list.save 92 | 93 | puts "dep: added #{lib}" 94 | end 95 | 96 | def rm(name) 97 | @list.remove(Dep::Lib.new(name)) 98 | @list.save 99 | 100 | puts "dep: removed #{name}" 101 | end 102 | 103 | def check 104 | if @list.missing_libraries.empty? 105 | puts "dep: all cool" 106 | else 107 | puts "dep: the following libraries are missing" 108 | 109 | @list.missing_libraries.each do |lib| 110 | puts " %s" % lib 111 | end 112 | 113 | exit(1) 114 | end 115 | end 116 | 117 | def install 118 | if @list.missing_libraries.empty? 119 | puts "dep: nothing to install" 120 | exit 121 | end 122 | 123 | run "gem install #{@list.missing_libraries.map(&:csv).join(" ")}" 124 | end 125 | 126 | def run(cmd) 127 | puts " #{cmd}" 128 | `#{cmd}` 129 | end 130 | end 131 | end 132 | 133 | module Kernel 134 | private 135 | def on(flag, &block) 136 | if index = ARGV.index(flag) 137 | _ = ARGV.delete_at(index) 138 | 139 | case block.arity 140 | when 1 then block.call(ARGV.delete_at(index)) 141 | when 0 then block.call 142 | else 143 | die 144 | end 145 | end 146 | end 147 | end 148 | 149 | # So originally, this was just $0 == __FILE__, but 150 | # since rubygems wraps the actual bin file in a loader 151 | # script, we have to instead rely on a different condition. 152 | if File.basename($0) == "dep" 153 | 154 | cli = Dep::CLI.new 155 | 156 | cli.file = File.join(Dir.pwd, ".gems") 157 | cli.prerelease = false 158 | 159 | on("-f") do |file| 160 | cli.file = file 161 | end 162 | 163 | on("--pre") do 164 | cli.prerelease = true 165 | end 166 | 167 | on("--help") do 168 | 169 | # We can't use DATA.read because rubygems does a wrapper script. 170 | help = File.read(__FILE__).split(/^__END__/)[1] 171 | 172 | IO.popen("less", "w") { |f| f.write(help) } 173 | exit 174 | end 175 | 176 | cli.list = Dep::List.new(cli.file) 177 | 178 | FileUtils.touch(cli.list.path) unless File.exist?(cli.list.path) 179 | 180 | case ARGV[0] 181 | when "add" 182 | cli.add(ARGV[1]) 183 | when "rm" 184 | cli.rm(ARGV[1]) 185 | when "install", "i" 186 | cli.install 187 | when nil 188 | cli.check 189 | else 190 | die 191 | end 192 | 193 | end 194 | 195 | __END__ 196 | DEP(1) 197 | 198 | NAME 199 | dep -- Basic dependency tracking 200 | 201 | SYNOPSIS 202 | dep 203 | dep add libname [--pre] 204 | dep rm libname 205 | dep install 206 | 207 | DESCRIPTION 208 | dep 209 | Checks that all dependencies are met. 210 | 211 | dep add [gemname] 212 | Fetches the latest version of `gemname` 213 | and automatically adds it to your .gems file. 214 | 215 | rm 216 | Removes the corresponding entry in your .gems file. 217 | 218 | install 219 | Installs all the missing dependencies for you. An important 220 | point here is that it simply does a `gem install` for each 221 | dependency you have. Dep assumes that you use some form of 222 | sandboxing like gs, rbenv-gemset or RVM gemsets. 223 | 224 | 225 | INSTALLATION 226 | $ wget -qO- http://amakawa.org/sh/install.sh | sh 227 | 228 | # or 229 | 230 | $ gem install dep 231 | 232 | HISTORY 233 | dep is actually more of a workflow than a tool. If you think about 234 | package managers and the problem of dependencies, you can summarize 235 | what you absolutely need from them in just two points: 236 | 237 | 1. When you build an application which relies on 3rd party libraries, 238 | it's best to explicitly declare the version numbers of these 239 | libraries. 240 | 241 | 2. You can either bundle the specific library version together with 242 | your application, or you can have a list of versions. 243 | 244 | The first approach is handled by vendoring the library. The second 245 | approach typically is done using Bundler. But why do you need such 246 | a complicated tool when all you need is simply listing version numbers? 247 | 248 | We dissected what we were doing and eventually reached the following 249 | workflow: 250 | 251 | 1. We maintain a .gems file for every application which lists the 252 | libraries and the version numbers. 253 | 2. We omit dependencies of dependencies in that file, the reason being 254 | is that that should already be handled by the package manager 255 | (typically rubygems). 256 | 3. Whenever we add a new library, we add the latest version. 257 | 4. When we pull the latest changes, we want to be able to rapidly 258 | check if the dependencies we have is up to date and matches what 259 | we just pulled. 260 | 261 | So after doing this workflow manually for a while, we decided to 262 | build the simplest tool to aid us with our workflow. 263 | 264 | The first point is handled implicitly by dep. You can also specify 265 | a different file by doing dep -f. 266 | 267 | The second point is more of an implementation detail. We thought about 268 | doing dependencies, but then, why re-implement something that's already 269 | done for you by rubygems? 270 | 271 | The third point (and also the one which is most inconvenient), is 272 | handled by dep add. 273 | 274 | The manual workflow for that would be: 275 | 276 | gem search -r "^ohm$" [--pre] # check and remember the version number 277 | echo "ohm -v X.x.x" >> .gems 278 | 279 | If you try doing that repeatedly, it will quickly become cumbersome. 280 | 281 | The fourth and final point is handled by typing dep check or simply dep. 282 | Practically speaking it's just: 283 | 284 | git pull 285 | dep 286 | 287 | And that's it. The dep command typically happens in 0.2 seconds which 288 | is something we LOVE. 289 | --------------------------------------------------------------------------------