librarianp 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +10 -0
  5. data/CHANGELOG.md +255 -0
  6. data/Gemfile +8 -0
  7. data/Gemfile.lock +235 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +55 -0
  10. data/Rakefile +28 -0
  11. data/VERSION +1 -0
  12. data/lib/librarian/action/base.rb +24 -0
  13. data/lib/librarian/action/clean.rb +44 -0
  14. data/lib/librarian/action/ensure.rb +24 -0
  15. data/lib/librarian/action/install.rb +95 -0
  16. data/lib/librarian/action/persist_resolution_mixin.rb +51 -0
  17. data/lib/librarian/action/resolve.rb +46 -0
  18. data/lib/librarian/action/update.rb +44 -0
  19. data/lib/librarian/action.rb +5 -0
  20. data/lib/librarian/algorithms.rb +133 -0
  21. data/lib/librarian/cli/manifest_presenter.rb +89 -0
  22. data/lib/librarian/cli.rb +225 -0
  23. data/lib/librarian/config/database.rb +205 -0
  24. data/lib/librarian/config/file_source.rb +47 -0
  25. data/lib/librarian/config/hash_source.rb +33 -0
  26. data/lib/librarian/config/source.rb +149 -0
  27. data/lib/librarian/config.rb +7 -0
  28. data/lib/librarian/dependency.rb +153 -0
  29. data/lib/librarian/dsl/receiver.rb +42 -0
  30. data/lib/librarian/dsl/target.rb +171 -0
  31. data/lib/librarian/dsl.rb +102 -0
  32. data/lib/librarian/environment/runtime_cache.rb +101 -0
  33. data/lib/librarian/environment.rb +230 -0
  34. data/lib/librarian/error.rb +4 -0
  35. data/lib/librarian/helpers.rb +29 -0
  36. data/lib/librarian/linter/source_linter.rb +55 -0
  37. data/lib/librarian/lockfile/compiler.rb +66 -0
  38. data/lib/librarian/lockfile/parser.rb +123 -0
  39. data/lib/librarian/lockfile.rb +29 -0
  40. data/lib/librarian/logger.rb +46 -0
  41. data/lib/librarian/manifest.rb +146 -0
  42. data/lib/librarian/manifest_set.rb +150 -0
  43. data/lib/librarian/mock/cli.rb +19 -0
  44. data/lib/librarian/mock/dsl.rb +15 -0
  45. data/lib/librarian/mock/environment.rb +21 -0
  46. data/lib/librarian/mock/extension.rb +9 -0
  47. data/lib/librarian/mock/source/mock/registry.rb +83 -0
  48. data/lib/librarian/mock/source/mock.rb +80 -0
  49. data/lib/librarian/mock/source.rb +1 -0
  50. data/lib/librarian/mock/version.rb +5 -0
  51. data/lib/librarian/mock.rb +1 -0
  52. data/lib/librarian/posix.rb +129 -0
  53. data/lib/librarian/resolution.rb +46 -0
  54. data/lib/librarian/resolver/implementation.rb +238 -0
  55. data/lib/librarian/resolver.rb +94 -0
  56. data/lib/librarian/rspec/support/cli_macro.rb +120 -0
  57. data/lib/librarian/source/basic_api.rb +45 -0
  58. data/lib/librarian/source/git/repository.rb +193 -0
  59. data/lib/librarian/source/git.rb +172 -0
  60. data/lib/librarian/source/local.rb +54 -0
  61. data/lib/librarian/source/path.rb +56 -0
  62. data/lib/librarian/source.rb +2 -0
  63. data/lib/librarian/spec.rb +13 -0
  64. data/lib/librarian/spec_change_set.rb +173 -0
  65. data/lib/librarian/specfile.rb +19 -0
  66. data/lib/librarian/support/abstract_method.rb +21 -0
  67. data/lib/librarian/ui.rb +64 -0
  68. data/lib/librarian/version.rb +3 -0
  69. data/lib/librarian.rb +11 -0
  70. data/librarian.gemspec +47 -0
  71. data/spec/functional/cli_spec.rb +27 -0
  72. data/spec/functional/posix_spec.rb +32 -0
  73. data/spec/functional/source/git/repository_spec.rb +199 -0
  74. data/spec/functional/source/git_spec.rb +174 -0
  75. data/spec/support/fakefs.rb +37 -0
  76. data/spec/support/method_patch_macro.rb +30 -0
  77. data/spec/support/project_path_macro.rb +14 -0
  78. data/spec/support/with_env_macro.rb +22 -0
  79. data/spec/unit/action/base_spec.rb +18 -0
  80. data/spec/unit/action/clean_spec.rb +102 -0
  81. data/spec/unit/action/ensure_spec.rb +37 -0
  82. data/spec/unit/action/install_spec.rb +111 -0
  83. data/spec/unit/algorithms_spec.rb +131 -0
  84. data/spec/unit/config/database_spec.rb +320 -0
  85. data/spec/unit/dependency/requirement_spec.rb +12 -0
  86. data/spec/unit/dependency_spec.rb +212 -0
  87. data/spec/unit/dsl_spec.rb +173 -0
  88. data/spec/unit/environment/runtime_cache_spec.rb +73 -0
  89. data/spec/unit/environment_spec.rb +209 -0
  90. data/spec/unit/lockfile/parser_spec.rb +162 -0
  91. data/spec/unit/lockfile_spec.rb +65 -0
  92. data/spec/unit/manifest/version_spec.rb +11 -0
  93. data/spec/unit/manifest_set_spec.rb +202 -0
  94. data/spec/unit/manifest_spec.rb +36 -0
  95. data/spec/unit/mock/environment_spec.rb +25 -0
  96. data/spec/unit/mock/source/mock_spec.rb +22 -0
  97. data/spec/unit/resolver_spec.rb +299 -0
  98. data/spec/unit/source/git_spec.rb +29 -0
  99. data/spec/unit/spec_change_set_spec.rb +169 -0
  100. metadata +257 -0
@@ -0,0 +1,146 @@
1
+ require 'rubygems'
2
+
3
+ module Librarian
4
+ class Manifest
5
+
6
+ class Version
7
+ include Comparable
8
+
9
+ def initialize(*args)
10
+ args = initialize_normalize_args(args)
11
+
12
+ self.backing = Gem::Version.new(*args)
13
+ end
14
+
15
+ def to_gem_version
16
+ backing
17
+ end
18
+
19
+ def <=>(other)
20
+ to_gem_version <=> other.to_gem_version
21
+ end
22
+
23
+ def to_s
24
+ to_gem_version.to_s
25
+ end
26
+
27
+ def inspect
28
+ "#<#{self.class} #{to_s}>"
29
+ end
30
+
31
+ private
32
+
33
+ def initialize_normalize_args(args)
34
+ args.map do |arg|
35
+ arg = [arg] if self.class === arg
36
+ arg
37
+ end
38
+ end
39
+
40
+ attr_accessor :backing
41
+ end
42
+
43
+ attr_accessor :source, :name, :extra
44
+ private :source=, :name=, :extra=
45
+
46
+ def initialize(source, name, extra = nil)
47
+ assert_name_valid! name
48
+
49
+ self.source = source
50
+ self.name = name
51
+ self.extra = extra
52
+ end
53
+
54
+ def to_s
55
+ "#{name}/#{version} <#{source}>"
56
+ end
57
+
58
+ def version
59
+ defined_version || fetched_version
60
+ end
61
+
62
+ def version=(version)
63
+ self.defined_version = _normalize_version(version)
64
+ end
65
+
66
+ def version?
67
+ return unless defined_version
68
+
69
+ defined_version == fetched_version
70
+ end
71
+
72
+ def latest
73
+ @latest ||= source.manifests(name).first
74
+ end
75
+
76
+ def outdated?
77
+ latest.version > version
78
+ end
79
+
80
+ def dependencies
81
+ defined_dependencies || fetched_dependencies
82
+ end
83
+
84
+ def dependencies=(dependencies)
85
+ self.defined_dependencies = _normalize_dependencies(dependencies)
86
+ end
87
+
88
+ def dependencies?
89
+ return unless defined_dependencies
90
+
91
+ defined_dependencies.zip(fetched_dependencies).all? do |(a, b)|
92
+ a.name == b.name && a.requirement == b.requirement
93
+ end
94
+ end
95
+
96
+ def satisfies?(dependency)
97
+ dependency.requirement.satisfied_by?(version)
98
+ end
99
+
100
+ def install!
101
+ source.install!(self)
102
+ end
103
+
104
+ private
105
+
106
+ attr_accessor :defined_version, :defined_dependencies
107
+
108
+ def environment
109
+ source.environment
110
+ end
111
+
112
+ def fetched_version
113
+ @fetched_version ||= _normalize_version(fetch_version!)
114
+ end
115
+
116
+ def fetched_dependencies
117
+ @fetched_dependencies ||= _normalize_dependencies(fetch_dependencies!)
118
+ end
119
+
120
+ def fetch_version!
121
+ source.fetch_version(name, extra)
122
+ end
123
+
124
+ def fetch_dependencies!
125
+ source.fetch_dependencies(name, version, extra)
126
+ end
127
+
128
+ def _normalize_version(version)
129
+ Version.new(version)
130
+ end
131
+
132
+ def _normalize_dependencies(dependencies)
133
+ if Hash === dependencies
134
+ dependencies = dependencies.map{|k, v| Dependency.new(k, v, nil)}
135
+ end
136
+ dependencies.sort_by(&:name)
137
+ end
138
+
139
+ def assert_name_valid!(name)
140
+ name =~ /\A\S(?:.*\S)?\z/ and return
141
+
142
+ raise ArgumentError, "name (#{name.inspect}) must be sensible"
143
+ end
144
+
145
+ end
146
+ end
@@ -0,0 +1,150 @@
1
+ require "librarian/algorithms"
2
+
3
+ module Librarian
4
+ class ManifestSet
5
+
6
+ class << self
7
+ def shallow_strip(manifests, names)
8
+ new(manifests).shallow_strip!(names).send(method_for(manifests))
9
+ end
10
+ def deep_strip(manifests, names)
11
+ new(manifests).deep_strip!(names).send(method_for(manifests))
12
+ end
13
+ def shallow_keep(manifests, names)
14
+ new(manifests).shallow_keep!(names).send(method_for(manifests))
15
+ end
16
+ def deep_keep(manifests, names)
17
+ new(manifests).deep_keep!(names).send(method_for(manifests))
18
+ end
19
+ def cyclic?(manifests)
20
+ manifests = Hash[manifests.map{|m| [m.name, m]}] if Array === manifests
21
+ manifest_pairs = Hash[manifests.map{|k, m| [k, m.dependencies.map{|d| d.name}]}]
22
+ adj_algs.cyclic?(manifest_pairs)
23
+ end
24
+ def sort(manifests)
25
+ manifests = Hash[manifests.map{|m| [m.name, m]}] if Array === manifests
26
+ manifest_pairs = Hash[manifests.map{|k, m| [k, m.dependencies.map{|d| d.name}]}]
27
+ manifest_names = adj_algs.tsort_cyclic(manifest_pairs)
28
+ manifest_names.map{|n| manifests[n]}
29
+ end
30
+ private
31
+ def method_for(manifests)
32
+ case manifests
33
+ when Hash
34
+ :to_hash
35
+ when Array
36
+ :to_a
37
+ end
38
+ end
39
+ def adj_algs
40
+ Algorithms::AdjacencyListDirectedGraph
41
+ end
42
+ end
43
+
44
+ def initialize(manifests)
45
+ self.index = Hash === manifests ? manifests.dup : index_by(manifests, &:name)
46
+ end
47
+
48
+ def to_a
49
+ index.values
50
+ end
51
+
52
+ def to_hash
53
+ index.dup
54
+ end
55
+
56
+ def dup
57
+ self.class.new(index)
58
+ end
59
+
60
+ def shallow_strip(names)
61
+ dup.shallow_strip!(names)
62
+ end
63
+
64
+ def shallow_strip!(names)
65
+ assert_strings!(names)
66
+
67
+ names.each do |name|
68
+ index.delete(name)
69
+ end
70
+ self
71
+ end
72
+
73
+ def deep_strip(names)
74
+ dup.deep_strip!(names)
75
+ end
76
+
77
+ def deep_strip!(names)
78
+ strippables = dependencies_of(names)
79
+ shallow_strip!(strippables)
80
+
81
+ self
82
+ end
83
+
84
+ def shallow_keep(names)
85
+ dup.shallow_keep!(names)
86
+ end
87
+
88
+ def shallow_keep!(names)
89
+ assert_strings!(names)
90
+
91
+ names = Set.new(names) unless Set === names
92
+ index.reject! { |k, v| !names.include?(k) }
93
+ self
94
+ end
95
+
96
+ def deep_keep(names)
97
+ dup.conservative_strip!(names)
98
+ end
99
+
100
+ def deep_keep!(names)
101
+ keepables = dependencies_of(names)
102
+ shallow_keep!(keepables)
103
+
104
+ self
105
+ end
106
+
107
+ def consistent?
108
+ index.values.all? do |manifest|
109
+ in_compliance_with?(manifest.dependencies)
110
+ end
111
+ end
112
+
113
+ def in_compliance_with?(dependencies)
114
+ dependencies.all? do |dependency|
115
+ manifest = index[dependency.name]
116
+ manifest && manifest.satisfies?(dependency)
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ attr_accessor :index
123
+
124
+ def assert_strings!(names)
125
+ non_strings = names.reject{|name| String === name}
126
+ non_strings.empty? or raise TypeError, "names must all be strings"
127
+ end
128
+
129
+ # Straightforward breadth-first graph traversal algorithm.
130
+ def dependencies_of(names)
131
+ names = Array === names ? names.dup : names.to_a
132
+ assert_strings!(names)
133
+
134
+ deps = Set.new
135
+ until names.empty?
136
+ name = names.shift
137
+ next if deps.include?(name)
138
+
139
+ deps << name
140
+ names.concat index[name].dependencies.map(&:name)
141
+ end
142
+ deps.to_a
143
+ end
144
+
145
+ def index_by(enum)
146
+ Hash[enum.map{|obj| [yield(obj), obj]}]
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,19 @@
1
+ require 'librarian/cli'
2
+ require 'librarian/mock'
3
+
4
+ module Librarian
5
+ module Mock
6
+ class Cli < Librarian::Cli
7
+
8
+ module Particularity
9
+ def root_module
10
+ Mock
11
+ end
12
+ end
13
+
14
+ include Particularity
15
+ extend Particularity
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ require 'librarian/dsl'
2
+ require 'librarian/mock/source'
3
+
4
+ module Librarian
5
+ module Mock
6
+ class Dsl < Librarian::Dsl
7
+
8
+ dependency :dep
9
+
10
+ source :src => Source::Mock
11
+
12
+ shortcut :a, :src => 'source-a'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ require "librarian/environment"
2
+ require "librarian/mock/dsl"
3
+ require "librarian/mock/version"
4
+
5
+ module Librarian
6
+ module Mock
7
+ class Environment < Environment
8
+
9
+ def install_path
10
+ nil
11
+ end
12
+
13
+ def registry(options = nil, &block)
14
+ @registry ||= Source::Mock::Registry.new
15
+ @registry.merge!(options, &block)
16
+ @registry
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ require 'librarian/mock/environment'
2
+
3
+ module Librarian
4
+ module Mock
5
+ extend self
6
+ extend Librarian
7
+
8
+ end
9
+ end
@@ -0,0 +1,83 @@
1
+ module Librarian
2
+ module Mock
3
+ module Source
4
+ class Mock
5
+ class Registry
6
+
7
+ module Dsl
8
+
9
+ class Top
10
+ def initialize(sources)
11
+ @sources = sources
12
+ end
13
+ def source(name, &block)
14
+ @sources[name] ||= {}
15
+ Source.new(@sources[name]).instance_eval(&block) if block
16
+ end
17
+ end
18
+
19
+ class Source
20
+ def initialize(source)
21
+ @source = source
22
+ end
23
+ def spec(name, version = nil, &block)
24
+ @source[name] ||= []
25
+ unless version
26
+ Spec.new(@source[name]).instance_eval(&block) if block
27
+ else
28
+ Spec.new(@source[name]).version(version, &block)
29
+ end
30
+ @source[name] = @source[name].sort_by{|a| Manifest::Version.new(a[:version])}.reverse
31
+ end
32
+ end
33
+
34
+ class Spec
35
+ def initialize(spec)
36
+ @spec = spec
37
+ end
38
+ def version(name, &block)
39
+ @spec << { :version => name, :dependencies => {} }
40
+ Version.new(@spec.last[:dependencies]).instance_eval(&block) if block
41
+ end
42
+ end
43
+
44
+ class Version
45
+ def initialize(version)
46
+ @version = version
47
+ end
48
+ def dependency(name, *requirement)
49
+ @version[name] = requirement
50
+ end
51
+ end
52
+
53
+ class << self
54
+ def run!(sources, &block)
55
+ Top.new(sources).instance_eval(&block) if block
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ def initialize
62
+ clear!
63
+ end
64
+ def clear!
65
+ self.sources = { }
66
+ end
67
+ def merge!(options = nil, &block)
68
+ clear! if options && options[:clear]
69
+ Dsl.run!(sources, &block) if block
70
+ end
71
+ def [](name)
72
+ sources[name] ||= {}
73
+ end
74
+
75
+ private
76
+
77
+ attr_accessor :sources
78
+
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,80 @@
1
+ require 'librarian/manifest'
2
+ require 'librarian/source/basic_api'
3
+ require 'librarian/mock/source/mock/registry'
4
+
5
+ module Librarian
6
+ module Mock
7
+ module Source
8
+ class Mock
9
+ include Librarian::Source::BasicApi
10
+
11
+ lock_name 'MOCK'
12
+ spec_options []
13
+
14
+ attr_accessor :environment
15
+ private :environment=
16
+ attr_reader :name
17
+
18
+ def initialize(environment, name, options)
19
+ self.environment = environment
20
+ @name = name
21
+ end
22
+
23
+ def to_s
24
+ name
25
+ end
26
+
27
+ def ==(other)
28
+ other &&
29
+ self.class == other.class &&
30
+ self.name == other.name
31
+ end
32
+
33
+ def to_spec_args
34
+ [name, {}]
35
+ end
36
+
37
+ def to_lock_options
38
+ {:remote => name}
39
+ end
40
+
41
+ def registry
42
+ environment.registry[name]
43
+ end
44
+
45
+ def manifest(name, version, dependencies)
46
+ manifest = Manifest.new(self, name)
47
+ manifest.version = version
48
+ manifest.dependencies = dependencies
49
+ manifest
50
+ end
51
+
52
+ def manifests(name)
53
+ if d = registry[name]
54
+ d.map{|v| manifest(name, v[:version], v[:dependencies])}
55
+ else
56
+ nil
57
+ end
58
+ end
59
+
60
+ def install!(manifest)
61
+ end
62
+
63
+ def to_s
64
+ name
65
+ end
66
+
67
+ def fetch_version(name, extra)
68
+ extra
69
+ end
70
+
71
+ def fetch_dependencies(name, version, extra)
72
+ d = registry[name]
73
+ m = d.find{|v| v[:version] == version.to_s}
74
+ m[:dependencies]
75
+ end
76
+
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1 @@
1
+ require 'librarian/mock/source/mock'
@@ -0,0 +1,5 @@
1
+ module Librarian
2
+ module Mock
3
+ VERSION = "0.1.2"
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require 'librarian/mock/extension'
@@ -0,0 +1,129 @@
1
+ require "open3"
2
+
3
+ require "librarian/error"
4
+
5
+ module Librarian
6
+ module Posix
7
+
8
+ class << self
9
+
10
+ # Cross-platform way of finding an executable in the $PATH.
11
+ #
12
+ # which('ruby') #=> /usr/bin/ruby
13
+ #
14
+ # From:
15
+ # https://github.com/defunkt/hub/commit/353031307e704d860826fc756ff0070be5e1b430#L2R173
16
+ def which(cmd)
17
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(';') : ['']
18
+ ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
19
+ path = File.expand_path(path)
20
+ exts.each do |ext|
21
+ exe = File.join(path, cmd + ext)
22
+ return exe if File.file?(exe) && File.executable?(exe)
23
+ end
24
+ end
25
+ nil
26
+ end
27
+
28
+ def which!(cmd)
29
+ which(cmd) or raise Error, "cannot find #{cmd}"
30
+ end
31
+
32
+ end
33
+
34
+ class CommandFailure < Error
35
+ class << self
36
+ def raise!(command, status, message)
37
+ ex = new(message)
38
+ ex.command = command
39
+ ex.status = status
40
+ ex.set_backtrace caller
41
+ raise ex
42
+ end
43
+ end
44
+
45
+ attr_accessor :command, :status
46
+ end
47
+
48
+ class << self
49
+
50
+ if defined?(JRuby) # built with jruby-1.7.9 in mind
51
+
52
+ def rescuing(*klasses)
53
+ begin
54
+ yield
55
+ rescue *klasses
56
+ end
57
+ end
58
+
59
+ def run!(command, options = { })
60
+ out, err = nil, nil
61
+ chdir = options[:chdir].to_s if options[:chdir]
62
+ env = options[:env] || { }
63
+ old_env = Hash[env.keys.map{|k| [k, ENV[k]]}]
64
+ out, err, wait = nil, nil, nil
65
+ begin
66
+ ENV.update env
67
+ Dir.chdir(chdir || Dir.pwd) do
68
+ IO.popen3(*command) do |i, o, e, w|
69
+ rescuing(Errno::EBADF){ i.close } # jruby/1.9 can raise EBADF
70
+ out, err, wait = o.read, e.read, w
71
+ end
72
+ end
73
+ ensure
74
+ ENV.update old_env
75
+ end
76
+ s = wait ? wait.value : $? # wait is 1.9+-only
77
+ s.success? or CommandFailure.raise! command, s, err
78
+ out
79
+ end
80
+
81
+ else
82
+
83
+ if RUBY_VERSION < "1.9"
84
+
85
+ def run!(command, options = { })
86
+ i, o, e = IO.pipe, IO.pipe, IO.pipe
87
+ pid = fork do
88
+ $stdin.reopen i[0]
89
+ $stdout.reopen o[1]
90
+ $stderr.reopen e[1]
91
+ [i[1], i[0], o[0], e[0]].each &:close
92
+ ENV.update options[:env] || { }
93
+ Dir.chdir options[:chdir].to_s if options[:chdir]
94
+ exec *command
95
+ end
96
+ [i[0], i[1], o[1], e[1]].each &:close
97
+ Process.waitpid pid
98
+ $?.success? or CommandFailure.raise! command, $?, e[0].read
99
+ o[0].read
100
+ ensure
101
+ [i, o, e].flatten(1).each{|io| io.close unless io.closed?}
102
+ end
103
+
104
+ else
105
+
106
+ def run!(command, options = { })
107
+ i, o, e = IO.pipe, IO.pipe, IO.pipe
108
+ opts = {:in => i[0], :out => o[1], :err => e[1]}
109
+ opts[:chdir] = options[:chdir].to_s if options[:chdir]
110
+ command = command.dup
111
+ command.unshift options[:env] || { }
112
+ command.push opts
113
+ pid = Process.spawn(*command)
114
+ [i[0], i[1], o[1], e[1]].each &:close
115
+ Process.waitpid pid
116
+ $?.success? or CommandFailure.raise! command, $?, e[0].read
117
+ o[0].read
118
+ ensure
119
+ [i, o, e].flatten(1).each{|io| io.close unless io.closed?}
120
+ end
121
+
122
+ end
123
+
124
+ end
125
+
126
+ end
127
+
128
+ end
129
+ end