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,230 @@
1
+ require "pathname"
2
+ require 'net/http'
3
+ require "uri"
4
+ require "etc"
5
+
6
+ require "librarian/helpers"
7
+ require "librarian/support/abstract_method"
8
+
9
+ require "librarian/error"
10
+ require "librarian/config"
11
+ require "librarian/lockfile"
12
+ require "librarian/logger"
13
+ require "librarian/specfile"
14
+ require "librarian/resolver"
15
+ require "librarian/dsl"
16
+ require "librarian/source"
17
+ require "librarian/version"
18
+ require "librarian/environment/runtime_cache"
19
+
20
+ module Librarian
21
+ class Environment
22
+
23
+ include Support::AbstractMethod
24
+
25
+ attr_accessor :ui
26
+ attr_reader :runtime_cache
27
+
28
+ abstract_method :specfile_name, :dsl_class, :install_path
29
+
30
+ def initialize(options = { })
31
+ @pwd = options.fetch(:pwd) { Dir.pwd }
32
+ @env = options.fetch(:env) { ENV.to_hash }
33
+ @home = options.fetch(:home) { default_home }
34
+ @project_path = options[:project_path]
35
+ @runtime_cache = RuntimeCache.new
36
+ end
37
+
38
+ def logger
39
+ @logger ||= Logger.new(self)
40
+ end
41
+
42
+ def config_db
43
+ @config_db ||= begin
44
+ Config::Database.new(adapter_name,
45
+ :pwd => @pwd,
46
+ :env => @env,
47
+ :home => @home,
48
+ :project_path => @project_path,
49
+ :specfile_name => default_specfile_name
50
+ )
51
+ end
52
+ end
53
+
54
+ def default_specfile_name
55
+ @default_specfile_name ||= begin
56
+ capped = adapter_name.capitalize
57
+ "#{capped}file"
58
+ end
59
+ end
60
+
61
+ def project_path
62
+ config_db.project_path
63
+ end
64
+
65
+ def specfile_name
66
+ config_db.specfile_name
67
+ end
68
+
69
+ def specfile_path
70
+ config_db.specfile_path
71
+ end
72
+
73
+ def specfile
74
+ Specfile.new(self, specfile_path)
75
+ end
76
+
77
+ def adapter_module
78
+ implementation? or return
79
+ self.class.name.split("::")[0 ... -1].inject(Object, &:const_get)
80
+ end
81
+
82
+ def adapter_name
83
+ implementation? or return
84
+ Helpers.camel_cased_to_dasherized(self.class.name.split("::")[-2])
85
+ end
86
+
87
+ def adapter_version
88
+ implementation? or return
89
+ adapter_module::VERSION
90
+ end
91
+
92
+ def lockfile_name
93
+ config_db.lockfile_name
94
+ end
95
+
96
+ def lockfile_path
97
+ config_db.lockfile_path
98
+ end
99
+
100
+ def lockfile
101
+ Lockfile.new(self, lockfile_path)
102
+ end
103
+
104
+ def ephemeral_lockfile
105
+ Lockfile.new(self, nil)
106
+ end
107
+
108
+ def resolver(options = { })
109
+ Resolver.new(self, resolver_options.merge(options))
110
+ end
111
+
112
+ def resolver_options
113
+ {
114
+ :cyclic => resolver_permit_cyclic_reslutions?,
115
+ }
116
+ end
117
+
118
+ def resolver_permit_cyclic_reslutions?
119
+ false
120
+ end
121
+
122
+ def tmp_path
123
+ part = config_db["tmp"] || "tmp"
124
+ project_path.join(part)
125
+ end
126
+
127
+ def cache_path
128
+ tmp_path.join("librarian/cache")
129
+ end
130
+
131
+ def scratch_path
132
+ tmp_path.join("librarian/scratch")
133
+ end
134
+
135
+ def project_relative_path_to(path)
136
+ Pathname.new(path).relative_path_from(project_path)
137
+ end
138
+
139
+ def spec
140
+ specfile.read
141
+ end
142
+
143
+ def lock
144
+ lockfile.read
145
+ end
146
+
147
+ def dsl(*args, &block)
148
+ dsl_class.run(self, *args, &block)
149
+ end
150
+
151
+ def dsl_class
152
+ adapter_module::Dsl
153
+ end
154
+
155
+ def version
156
+ VERSION
157
+ end
158
+
159
+ def config_keys
160
+ %[
161
+ ]
162
+ end
163
+
164
+ # The HTTP proxy specified in the environment variables:
165
+ # * HTTP_PROXY
166
+ # * HTTP_PROXY_USER
167
+ # * HTTP_PROXY_PASS
168
+ # Adapted from:
169
+ # https://github.com/rubygems/rubygems/blob/v1.8.24/lib/rubygems/remote_fetcher.rb#L276-293
170
+ def http_proxy_uri
171
+ @http_proxy_uri ||= begin
172
+ keys = %w( HTTP_PROXY HTTP_PROXY_USER HTTP_PROXY_PASS )
173
+ env = Hash[ENV.
174
+ map{|k, v| [k.upcase, v]}.
175
+ select{|k, v| keys.include?(k)}.
176
+ reject{|k, v| v.nil? || v.empty?}]
177
+
178
+ uri = env["HTTP_PROXY"] or return
179
+ uri = "http://#{uri}" unless uri =~ /^(https?|ftp|file):/
180
+ uri = URI.parse(uri)
181
+ uri.user ||= env["HTTP_PROXY_USER"]
182
+ uri.password ||= env["HTTP_PROXY_PASS"]
183
+ uri
184
+ end
185
+ end
186
+
187
+ def net_http_class(host)
188
+ no_proxy?(host) ? Net::HTTP : net_http_default_class
189
+ end
190
+
191
+ def inspect
192
+ "#<#{self.class}:0x#{__id__.to_s(16)}>"
193
+ end
194
+
195
+ private
196
+
197
+ def environment
198
+ self
199
+ end
200
+
201
+ def implementation?
202
+ self.class != ::Librarian::Environment
203
+ end
204
+
205
+ def default_home
206
+ File.expand_path(ENV["HOME"] || Etc.getpwnam(Etc.getlogin).dir)
207
+ end
208
+
209
+ def no_proxy_list
210
+ @no_proxy_list ||= begin
211
+ list = ENV['NO_PROXY'] || ENV['no_proxy'] || ""
212
+ list.split(/\s*,\s*/) + %w(localhost 127.0.0.1)
213
+ end
214
+ end
215
+
216
+ def no_proxy?(host)
217
+ no_proxy_list.any? do |host_addr|
218
+ host.end_with?(host_addr)
219
+ end
220
+ end
221
+
222
+ def net_http_default_class
223
+ @net_http_default_class ||= begin
224
+ p = http_proxy_uri
225
+ p ? Net::HTTP::Proxy(p.host, p.port, p.user, p.password) : Net::HTTP
226
+ end
227
+ end
228
+
229
+ end
230
+ end
@@ -0,0 +1,4 @@
1
+ module Librarian
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,29 @@
1
+ module Librarian
2
+
3
+ # PRIVATE
4
+ #
5
+ # Adapters must not rely on these methods since they will change.
6
+ #
7
+ # Adapters requiring similar methods ought to re-implement them.
8
+ module Helpers
9
+ extend self
10
+
11
+ # [active_support/core_ext/string/strip]
12
+ def strip_heredoc(string)
13
+ indent = string.scan(/^[ \t]*(?=\S)/).min
14
+ indent = indent.respond_to?(:size) ? indent.size : 0
15
+ string.gsub(/^[ \t]{#{indent}}/, '')
16
+ end
17
+
18
+ # [active_support/inflector/methods]
19
+ def camel_cased_to_dasherized(camel_cased_word)
20
+ word = camel_cased_word.to_s.dup
21
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1-\2')
22
+ word.gsub!(/([a-z\d])([A-Z])/,'\1-\2')
23
+ word.downcase!
24
+ word
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,55 @@
1
+ module Librarian
2
+ module Linter
3
+ class SourceLinter
4
+
5
+ class << self
6
+ def lint!(klass)
7
+ new(klass).lint!
8
+ end
9
+ end
10
+
11
+ attr_accessor :klass
12
+ private :klass=
13
+
14
+ def initialize(klass)
15
+ self.klass = klass
16
+ end
17
+
18
+ def lint!
19
+ lint_class_responds_to! *[
20
+ :lock_name,
21
+ :from_spec_args,
22
+ :from_lock_options,
23
+ ]
24
+
25
+ lint_instance_responds_to! *[
26
+ :to_spec_args,
27
+ :to_lock_options,
28
+ :manifests,
29
+ :fetch_version,
30
+ :fetch_dependencies,
31
+ :pinned?,
32
+ :unpin!,
33
+ :install!,
34
+ ]
35
+ end
36
+
37
+ private
38
+
39
+ def lint_class_responds_to!(*names)
40
+ missing = names.reject{|name| klass.respond_to?(name)}
41
+ return if missing.empty?
42
+
43
+ raise "class must respond to #{missing.join(', ')}"
44
+ end
45
+
46
+ def lint_instance_responds_to!(*names)
47
+ missing = names - klass.public_instance_methods.map(&:to_sym)
48
+ return if missing.empty?
49
+
50
+ raise "instance must respond to #{missing.join(', ')}"
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,66 @@
1
+ module Librarian
2
+ class Lockfile
3
+ class Compiler
4
+
5
+ attr_accessor :environment
6
+ private :environment=
7
+
8
+ def initialize(environment)
9
+ self.environment = environment
10
+ end
11
+
12
+ def compile(resolution)
13
+ out = StringIO.new
14
+ save_sources(out, resolution.manifests)
15
+ save_dependencies(out, resolution.dependencies)
16
+ out.string
17
+ end
18
+
19
+ private
20
+
21
+ def save_sources(out, manifests)
22
+ dsl_class.source_types.map{|t| t[1]}.each do |type|
23
+ type_manifests = manifests.select{|m| type === m.source}
24
+ sources = type_manifests.map{|m| m.source}.uniq.sort_by{|s| s.to_s}
25
+ sources.each do |source|
26
+ source_manifests = type_manifests.select{|m| source == m.source}
27
+ save_source(out, source, source_manifests)
28
+ end
29
+ end
30
+ end
31
+
32
+ def save_source(out, source, manifests)
33
+ out.puts "#{source.class.lock_name}"
34
+ options = source.to_lock_options
35
+ remote = options.delete(:remote)
36
+ out.puts " remote: #{remote}"
37
+ options.to_a.sort_by{|a| a[0].to_s}.each do |o|
38
+ out.puts " #{o[0]}: #{o[1]}"
39
+ end
40
+ out.puts " specs:"
41
+ manifests.sort_by{|a| a.name}.each do |manifest|
42
+ out.puts " #{manifest.name} (#{manifest.version})"
43
+ manifest.dependencies.sort_by{|a| a.name}.each do |dependency|
44
+ out.puts " #{dependency.name} (#{dependency.requirement})"
45
+ end
46
+ end
47
+ out.puts ""
48
+ end
49
+
50
+ def save_dependencies(out, dependencies)
51
+ out.puts "DEPENDENCIES"
52
+ dependencies.sort_by{|a| a.name}.each do |d|
53
+ res = "#{d.name}"
54
+ res << " (#{d.requirement})" if d.requirement
55
+ out.puts " #{res}"
56
+ end
57
+ out.puts ""
58
+ end
59
+
60
+ def dsl_class
61
+ environment.dsl_class
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,123 @@
1
+ require 'librarian/manifest'
2
+ require 'librarian/dependency'
3
+ require 'librarian/manifest_set'
4
+
5
+ module Librarian
6
+ class Lockfile
7
+ class Parser
8
+
9
+ class ManifestPlaceholder
10
+ attr_reader :source, :name, :version, :dependencies
11
+ def initialize(source, name, version, dependencies)
12
+ @source, @name, @version, @dependencies = source, name, version, dependencies
13
+ end
14
+ end
15
+
16
+ attr_accessor :environment
17
+ private :environment=
18
+
19
+ def initialize(environment)
20
+ self.environment = environment
21
+ end
22
+
23
+ def parse(string)
24
+ lines = string.lines.map{|l| l.sub(/\s+\z/, '')}.reject(&:empty?)
25
+ sources = extract_and_parse_sources(lines)
26
+ manifests = compile(sources)
27
+ manifests_index = Hash[manifests.map{|m| [m.name, m]}]
28
+ raise StandardError, "Expected DEPENDENCIES topic!" unless lines.shift == "DEPENDENCIES"
29
+ dependencies = extract_and_parse_dependencies(lines, manifests_index)
30
+ Resolution.new(dependencies, manifests)
31
+ end
32
+
33
+ private
34
+
35
+ def extract_and_parse_sources(lines)
36
+ sources = []
37
+ while source_type_names.include?(lines.first)
38
+ source = {}
39
+ source_type_name = lines.shift
40
+ source[:type] = source_type_names_map[source_type_name]
41
+ options = {}
42
+ while lines.first =~ /^ {2}([\w-]+):\s+(.+)$/
43
+ lines.shift
44
+ options[$1.to_sym] = $2
45
+ end
46
+ source[:options] = options
47
+ lines.shift # specs
48
+ manifests = {}
49
+ while lines.first =~ /^ {4}([\w-]+) \((.*)\)$/
50
+ lines.shift
51
+ name = $1
52
+ manifests[name] = {:version => $2, :dependencies => {}}
53
+ while lines.first =~ /^ {6}([\w-]+) \((.*)\)$/
54
+ lines.shift
55
+ manifests[name][:dependencies][$1] = $2.split(/,\s*/)
56
+ end
57
+ end
58
+ source[:manifests] = manifests
59
+ sources << source
60
+ end
61
+ sources
62
+ end
63
+
64
+ def extract_and_parse_dependencies(lines, manifests_index)
65
+ dependencies = []
66
+ while lines.first =~ /^ {2}([\w-]+)(?: \((.*)\))?$/
67
+ lines.shift
68
+ name, requirement = $1, $2.split(/,\s*/)
69
+ dependencies << Dependency.new(name, requirement, manifests_index[name].source)
70
+ end
71
+ dependencies
72
+ end
73
+
74
+ def compile_placeholder_manifests(sources_ast)
75
+ manifests = {}
76
+ sources_ast.each do |source_ast|
77
+ source_type = source_ast[:type]
78
+ source = source_type.from_lock_options(environment, source_ast[:options])
79
+ source_ast[:manifests].each do |manifest_name, manifest_ast|
80
+ manifests[manifest_name] = ManifestPlaceholder.new(
81
+ source,
82
+ manifest_name,
83
+ manifest_ast[:version],
84
+ manifest_ast[:dependencies].map{|k, v| Dependency.new(k, v, nil)}
85
+ )
86
+ end
87
+ end
88
+ manifests
89
+ end
90
+
91
+ def compile(sources_ast)
92
+ manifests = compile_placeholder_manifests(sources_ast)
93
+ manifests = manifests.map do |name, manifest|
94
+ dependencies = manifest.dependencies.map do |d|
95
+ Dependency.new(d.name, d.requirement, manifests[d.name].source)
96
+ end
97
+ real = Manifest.new(manifest.source, manifest.name)
98
+ real.version = manifest.version
99
+ real.dependencies = manifest.dependencies
100
+ real
101
+ end
102
+ ManifestSet.sort(manifests)
103
+ end
104
+
105
+ def dsl_class
106
+ environment.dsl_class
107
+ end
108
+
109
+ def source_type_names_map
110
+ @source_type_names_map ||= begin
111
+ Hash[dsl_class.source_types.map{|t| [t[1].lock_name, t[1]]}]
112
+ end
113
+ end
114
+
115
+ def source_type_names
116
+ @source_type_names ||= begin
117
+ dsl_class.source_types.map{|t| t[1].lock_name}
118
+ end
119
+ end
120
+
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,29 @@
1
+ require 'librarian/lockfile/compiler'
2
+ require 'librarian/lockfile/parser'
3
+
4
+ module Librarian
5
+ class Lockfile
6
+
7
+ attr_accessor :environment
8
+ private :environment=
9
+ attr_reader :path
10
+
11
+ def initialize(environment, path)
12
+ self.environment = environment
13
+ @path = path
14
+ end
15
+
16
+ def save(resolution)
17
+ Compiler.new(environment).compile(resolution)
18
+ end
19
+
20
+ def load(string)
21
+ Parser.new(environment).parse(string)
22
+ end
23
+
24
+ def read
25
+ load(path.read)
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ module Librarian
2
+ class Logger
3
+
4
+ librarian_path = Pathname(__FILE__)
5
+ librarian_path = librarian_path.dirname until librarian_path.join("lib").directory?
6
+ LIBRARIAN_PATH = librarian_path
7
+
8
+ attr_accessor :environment
9
+ private :environment=
10
+
11
+ def initialize(environment)
12
+ self.environment = environment
13
+ end
14
+
15
+ def info(string = nil, &block)
16
+ return unless ui
17
+
18
+ ui.info(string || yield)
19
+ end
20
+
21
+ def debug(string = nil, &block)
22
+ return unless ui
23
+
24
+ if ui.respond_to?(:debug_line_numbers) && ui.debug_line_numbers
25
+ loc = caller.find{|l| !(l =~ /in `debug'$/)}
26
+ if loc =~ /^(.+):(\d+):in `(.+)'$/
27
+ loc = "#{Pathname.new($1).relative_path_from(LIBRARIAN_PATH)}:#{$2}:in `#{$3}'"
28
+ end
29
+ ui.debug { "[Librarian] #{string || yield} [#{loc}]" }
30
+ else
31
+ ui.debug { "[Librarian] #{string || yield}" }
32
+ end
33
+ end
34
+
35
+ def relative_path_to(path)
36
+ environment.project_relative_path_to(path)
37
+ end
38
+
39
+ private
40
+
41
+ def ui
42
+ environment.ui
43
+ end
44
+
45
+ end
46
+ end