librarianp 0.1.2

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 (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