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