elm_install 0.3.1 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.reek +4 -0
- data/.rspec +1 -0
- data/.rubocop.yml +4 -0
- data/Gemfile.lock +30 -25
- data/Rakefile +2 -2
- data/Readme.md +85 -23
- data/bin/elm-install +5 -2
- data/elm_install.gemspec +2 -0
- data/lib/elm_install.rb +18 -1
- data/lib/elm_install/base.rb +3 -46
- data/lib/elm_install/dependency.rb +37 -0
- data/lib/elm_install/directory_source.rb +66 -0
- data/lib/elm_install/ext.rb +23 -0
- data/lib/elm_install/git_source.rb +173 -0
- data/lib/elm_install/identifier.rb +133 -0
- data/lib/elm_install/installer.rb +38 -96
- data/lib/elm_install/populator.rb +54 -75
- data/lib/elm_install/repository.rb +82 -0
- data/lib/elm_install/resolver.rb +48 -118
- data/lib/elm_install/source.rb +18 -0
- data/lib/elm_install/types.rb +43 -0
- data/lib/elm_install/utils.rb +14 -20
- data/lib/elm_install/version.rb +1 -1
- data/package.json +1 -1
- data/packaging/Gemfile +1 -1
- data/packaging/Gemfile.lock +8 -4
- data/scripts/install.js +4 -4
- data/scripts/run.js +4 -4
- data/spec/directory_source_spec.rb +37 -0
- data/spec/{eml_install_spec.rb → elm_install_spec.rb} +6 -3
- data/spec/git_source_spec.rb +115 -0
- data/spec/identifer_spec.rb +53 -0
- data/spec/installer_spec.rb +57 -26
- data/spec/repository_spec.rb +44 -0
- data/spec/resolver_spec.rb +0 -73
- data/spec/spec_helper.rb +3 -1
- data/spec/utils_spec.rb +10 -15
- metadata +43 -17
- data/docs/How it works.md +0 -54
- data/lib/elm_install/cache.rb +0 -52
- data/lib/elm_install/elm_package.rb +0 -119
- data/lib/elm_install/git_resolver.rb +0 -129
- data/lib/elm_install/graph_builder.rb +0 -73
- data/spec/elm_package_spec.rb +0 -73
- data/spec/fixtures/cache.json +0 -8
- data/spec/fixtures/elm-package.json +0 -12
- data/spec/fixtures/invalid-elm-package.json +0 -6
- data/spec/fixtures/mismatched-elm-package.json +0 -9
- data/spec/fixtures/ref-cache.json +0 -4
- data/spec/git_resolver_spec.rb +0 -103
- data/spec/graph_builder_spec.rb +0 -36
- data/spec/populator_spec.rb +0 -44
@@ -0,0 +1,66 @@
|
|
1
|
+
module ElmInstall
|
2
|
+
# This clas handles sources that point to a local directory.
|
3
|
+
class DirectorySource < Source
|
4
|
+
# @return [Dir] The directory
|
5
|
+
attr_reader :dir
|
6
|
+
|
7
|
+
Contract Pathname => DirectorySource
|
8
|
+
# Initializes a directory source with the given directory.
|
9
|
+
#
|
10
|
+
# @param dir [Dir] The directory
|
11
|
+
#
|
12
|
+
# @return [DirectorySource] The directory source instance
|
13
|
+
def initialize(dir)
|
14
|
+
@dir = dir
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
Contract Or[String, Semverse::Version] => Dir
|
19
|
+
# Returns the directory
|
20
|
+
#
|
21
|
+
# @param _ [String] The version
|
22
|
+
#
|
23
|
+
# @return [Dir] The directory
|
24
|
+
def fetch(_)
|
25
|
+
Dir.new(@dir.expand_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
Contract Semverse::Version, Pathname => nil
|
29
|
+
# Copies the directory to the given other directory
|
30
|
+
#
|
31
|
+
# @param _ [Semverse::Version] The version
|
32
|
+
# @param directory [Pathname] The pathname
|
33
|
+
#
|
34
|
+
# @return nil
|
35
|
+
def copy_to(_, directory)
|
36
|
+
# Delete the directory to make sure no pervious version remains
|
37
|
+
FileUtils.rm_rf(directory) if directory.exist?
|
38
|
+
|
39
|
+
# Create parent directory
|
40
|
+
FileUtils.mkdir_p(directory.parent)
|
41
|
+
|
42
|
+
# Create symlink
|
43
|
+
FileUtils.ln_s(@dir.expand_path, directory)
|
44
|
+
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
Contract ArrayOf[Solve::Constraint] => ArrayOf[Semverse::Version]
|
49
|
+
# Returns the available versions for a repository
|
50
|
+
#
|
51
|
+
# @param _ [Array] The constraints
|
52
|
+
#
|
53
|
+
# @return [Array] The versions
|
54
|
+
def versions(_)
|
55
|
+
[identifier.version(fetch(''))]
|
56
|
+
end
|
57
|
+
|
58
|
+
Contract None => String
|
59
|
+
# Returns the log format
|
60
|
+
#
|
61
|
+
# @return [String]
|
62
|
+
def to_log
|
63
|
+
@dir.expand_path.to_s
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Extensions for the semvese module
|
2
|
+
module Semverse
|
3
|
+
# Added utility functions
|
4
|
+
class Version
|
5
|
+
# Returns the simple string representation of a version
|
6
|
+
#
|
7
|
+
# @return [String]
|
8
|
+
def to_simple
|
9
|
+
"#{major}.#{minor}.#{patch}"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Tries to parse a version, falling back to nil if fails.
|
13
|
+
#
|
14
|
+
# @param version [String] The version to parse
|
15
|
+
#
|
16
|
+
# @return [Semverse::Version]
|
17
|
+
def self.try_new(version)
|
18
|
+
new version
|
19
|
+
rescue
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module ElmInstall
|
2
|
+
# Git Source
|
3
|
+
class GitSource < Source
|
4
|
+
# @return [Uri] The uri
|
5
|
+
attr_reader :uri
|
6
|
+
|
7
|
+
# @return [Branch] The branch
|
8
|
+
attr_reader :branch
|
9
|
+
|
10
|
+
Contract Uri, Branch => GitSource
|
11
|
+
# Initializes a git source by URI and branch
|
12
|
+
#
|
13
|
+
# @param uri [Uri] The uri
|
14
|
+
# @param branch [Branch] The branch
|
15
|
+
#
|
16
|
+
# @return [GitSource]
|
17
|
+
def initialize(uri, branch)
|
18
|
+
@branch = branch
|
19
|
+
@uri = uri
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
Contract Or[String, Semverse::Version] => Dir
|
24
|
+
# Downloads the version into a temporary directory
|
25
|
+
#
|
26
|
+
# @param version [Semverse::Version] The version to fetch
|
27
|
+
#
|
28
|
+
# @return [Dir] The directory for the source of the version
|
29
|
+
def fetch(version)
|
30
|
+
# Get the reference from the branch
|
31
|
+
ref =
|
32
|
+
case @branch
|
33
|
+
when Branch::Just
|
34
|
+
@branch.ref
|
35
|
+
when Branch::Nothing
|
36
|
+
version.to_simple
|
37
|
+
end
|
38
|
+
|
39
|
+
repository.checkout ref
|
40
|
+
end
|
41
|
+
|
42
|
+
Contract Semverse::Version, Pathname => nil
|
43
|
+
# Copies the version into the given directory
|
44
|
+
#
|
45
|
+
# @param version [Semverse::Version] The version
|
46
|
+
# @param directory [Pathname] The pathname
|
47
|
+
#
|
48
|
+
# @return nil
|
49
|
+
def copy_to(version, directory)
|
50
|
+
# Delete the directory to make sure no pervious version remains if
|
51
|
+
# we are using a branch or symlink if using Dir.
|
52
|
+
FileUtils.rm_rf(directory) if directory.exist?
|
53
|
+
|
54
|
+
# Create directory if not exists
|
55
|
+
FileUtils.mkdir_p directory
|
56
|
+
|
57
|
+
# Copy hole repository
|
58
|
+
FileUtils.cp_r("#{fetch(version).path}/.", directory)
|
59
|
+
|
60
|
+
# Remove .git directory
|
61
|
+
FileUtils.rm_rf(File.join(directory, '.git'))
|
62
|
+
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
Contract ArrayOf[Solve::Constraint] => ArrayOf[Semverse::Version]
|
67
|
+
# Returns the available versions for a repository
|
68
|
+
#
|
69
|
+
# @param constraints [Array] The constraints
|
70
|
+
#
|
71
|
+
# @return [Array] The versions
|
72
|
+
def versions(constraints)
|
73
|
+
case @branch
|
74
|
+
when Branch::Just
|
75
|
+
[identifier.version(fetch(@branch.ref))]
|
76
|
+
when Branch::Nothing
|
77
|
+
matches = matching_versions constraints
|
78
|
+
return matches if matches.any?
|
79
|
+
Logger.arrow(
|
80
|
+
"Could not find matching versions for: #{package_name.bold}"\
|
81
|
+
' in cache. Fetching updates.'
|
82
|
+
)
|
83
|
+
repository.fetch
|
84
|
+
matching_versions constraints
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
Contract ArrayOf[Solve::Constraint] => ArrayOf[Semverse::Version]
|
89
|
+
# Returns the matchign versions for a repository for the given constraints
|
90
|
+
#
|
91
|
+
# @param constraints [Array] The constraints
|
92
|
+
#
|
93
|
+
# @return [Array] The versions
|
94
|
+
def matching_versions(constraints)
|
95
|
+
repository
|
96
|
+
.versions
|
97
|
+
.select do |version|
|
98
|
+
constraints.all? { |constraint| constraint.satisfies?(version) }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
Contract None => String
|
103
|
+
# Returns the url for the repository
|
104
|
+
#
|
105
|
+
# @return [String] The url
|
106
|
+
def url
|
107
|
+
@uri.to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
Contract None => String
|
111
|
+
# Returns the temporary path for the repository
|
112
|
+
#
|
113
|
+
# @return [String] The path
|
114
|
+
def path
|
115
|
+
File.join(options[:cache_directory].to_s, host, package_name)
|
116
|
+
end
|
117
|
+
|
118
|
+
Contract None => String
|
119
|
+
# Returns the host for the repository
|
120
|
+
#
|
121
|
+
# @return [String] The host
|
122
|
+
def host
|
123
|
+
case @uri
|
124
|
+
when Uri::Github
|
125
|
+
'github.com'
|
126
|
+
else
|
127
|
+
@uri.uri.host
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
Contract None => String
|
132
|
+
# Returns the package name for the repository
|
133
|
+
#
|
134
|
+
# @return [String] The name
|
135
|
+
def package_name
|
136
|
+
case @uri
|
137
|
+
when Uri::Github
|
138
|
+
@uri.name
|
139
|
+
else
|
140
|
+
@uri.uri.path.sub(%r{^/}, '')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
Contract None => Repository
|
145
|
+
# Returns the local repository
|
146
|
+
#
|
147
|
+
# @return [Repository] The repository
|
148
|
+
def repository
|
149
|
+
@repository ||= Repository.new url, path
|
150
|
+
end
|
151
|
+
|
152
|
+
Contract None => Or[String, NilClass]
|
153
|
+
# Returns the log format
|
154
|
+
#
|
155
|
+
# @return [String]
|
156
|
+
def to_log
|
157
|
+
case @uri
|
158
|
+
when Uri::Ssh, Uri::Http
|
159
|
+
case @branch
|
160
|
+
when Branch::Just
|
161
|
+
"#{url} at #{@branch.ref}"
|
162
|
+
else
|
163
|
+
# NOTE: Cannot happen
|
164
|
+
# :nocov:
|
165
|
+
url
|
166
|
+
# :nocov:
|
167
|
+
end
|
168
|
+
else
|
169
|
+
url
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module ElmInstall
|
2
|
+
# Identifies dependencies
|
3
|
+
class Identifier < Base
|
4
|
+
# @return [Array<Dependency>] The initial dependencies
|
5
|
+
attr_reader :initial_dependencies
|
6
|
+
|
7
|
+
# @return [Hash] The options
|
8
|
+
attr_reader :options
|
9
|
+
|
10
|
+
Contract Dir, Hash => Identifier
|
11
|
+
# Initialize a new identifier.
|
12
|
+
#
|
13
|
+
# @param directory [Dir] The initial directory
|
14
|
+
# @param options [Hash] The options
|
15
|
+
#
|
16
|
+
# @return [Indentifier] The identifier instance
|
17
|
+
def initialize(directory, options = {})
|
18
|
+
@options = options
|
19
|
+
@dependency_sources = dependency_sources directory
|
20
|
+
@initial_dependencies = identify directory
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
Contract Dir => HashOf[String => Any]
|
25
|
+
# Returns the dependency sources for the given directory.
|
26
|
+
#
|
27
|
+
# @param directory [Dir] The directory
|
28
|
+
#
|
29
|
+
# @return [Hash] The directory sources
|
30
|
+
def dependency_sources(directory)
|
31
|
+
json(directory)['dependency-sources'].to_h
|
32
|
+
end
|
33
|
+
|
34
|
+
Contract Dir => Semverse::Version
|
35
|
+
# Returns the version of a package in the given directory.
|
36
|
+
#
|
37
|
+
# @param directory [Dir] The directory
|
38
|
+
#
|
39
|
+
# @return [Semverse::Version] The version
|
40
|
+
def version(directory)
|
41
|
+
Semverse::Version.new(json(directory)['version'])
|
42
|
+
end
|
43
|
+
|
44
|
+
Contract Dir => ArrayOf[Dependency]
|
45
|
+
# Identifies dependencies from a directory
|
46
|
+
#
|
47
|
+
# @param directory [Dir] The directory
|
48
|
+
#
|
49
|
+
# @return [Array] The dependencies
|
50
|
+
def identify(directory)
|
51
|
+
raw = json(directory)
|
52
|
+
|
53
|
+
dependencies = raw['dependencies'].to_h
|
54
|
+
|
55
|
+
dependency_sources =
|
56
|
+
raw['dependency-sources']
|
57
|
+
.to_h
|
58
|
+
.merge(@dependency_sources)
|
59
|
+
|
60
|
+
dependencies.map do |package, constraint|
|
61
|
+
constraints = Utils.transform_constraint constraint
|
62
|
+
|
63
|
+
type =
|
64
|
+
if dependency_sources.key?(package)
|
65
|
+
source = dependency_sources[package]
|
66
|
+
case source
|
67
|
+
when Hash
|
68
|
+
uri_type source['url'], Branch::Just(source['ref'])
|
69
|
+
when String
|
70
|
+
if File.exist?(source)
|
71
|
+
Type::Directory(Pathname.new(source))
|
72
|
+
else
|
73
|
+
uri_type source, Branch::Just('master')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
else
|
77
|
+
Type::Git(Uri::Github(package), Branch::Nothing())
|
78
|
+
end
|
79
|
+
|
80
|
+
type.source.identifier = self
|
81
|
+
type.source.options = @options
|
82
|
+
|
83
|
+
Dependency.new(package, type.source, constraints)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
Contract String, Branch => Type
|
88
|
+
# Returns the type from the given arguments.
|
89
|
+
#
|
90
|
+
# @param url [String] The base url
|
91
|
+
# @param branch [Branch] The branch
|
92
|
+
#
|
93
|
+
# @return [Type] The type
|
94
|
+
def uri_type(url, branch)
|
95
|
+
uri = GitCloneUrl.parse(url)
|
96
|
+
case uri
|
97
|
+
when URI::SshGit::Generic
|
98
|
+
Type::Git(Uri::Ssh(uri), branch)
|
99
|
+
when URI::HTTP
|
100
|
+
Type::Git(Uri::Http(uri), branch)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
Contract Dir => HashOf[String => Any]
|
105
|
+
# Returns the contents of the 'elm-package.json' for the given directory.
|
106
|
+
#
|
107
|
+
# @param directory [Dir] The directory
|
108
|
+
#
|
109
|
+
# @return [Hash] The contents
|
110
|
+
def json(directory)
|
111
|
+
path = File.join(directory, 'elm-package.json')
|
112
|
+
JSON.parse(File.read(path))
|
113
|
+
rescue JSON::ParserError
|
114
|
+
exit "Invalid JSON in file: #{path.bold}"
|
115
|
+
{}
|
116
|
+
rescue Errno::ENOENT
|
117
|
+
exit "Could not find file: #{path.bold}"
|
118
|
+
{}
|
119
|
+
end
|
120
|
+
|
121
|
+
Contract String => NilClass
|
122
|
+
# Exits the current process and logs a given message.
|
123
|
+
#
|
124
|
+
# @param message [String] The message
|
125
|
+
#
|
126
|
+
# @return nil
|
127
|
+
def exit(message)
|
128
|
+
Logger.arrow message
|
129
|
+
Process.exit
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -1,118 +1,60 @@
|
|
1
|
-
require_relative './resolver'
|
2
|
-
require_relative './elm_package'
|
3
|
-
require_relative './git_resolver'
|
4
|
-
require_relative './graph_builder'
|
5
|
-
require_relative './populator'
|
6
|
-
|
7
1
|
module ElmInstall
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
# Initializes
|
2
|
+
# Installer class
|
3
|
+
class Installer < Base
|
4
|
+
Contract KeywordArgs[cache_directory: Or[String, NilClass],
|
5
|
+
verbose: Or[Bool, NilClass]] => Installer
|
6
|
+
# Initializes an installer with the given options
|
13
7
|
#
|
14
8
|
# @param options [Hash] The options
|
15
|
-
def initialize(options)
|
16
|
-
init_options options
|
17
|
-
@git_resolver = GitResolver.new directory: cache_directory
|
18
|
-
@cache = Cache.new directory: cache_directory
|
19
|
-
@populator = Populator.new @git_resolver
|
20
|
-
@options = options
|
21
|
-
end
|
22
|
-
|
23
|
-
# Initializes the options setting default values.
|
24
9
|
#
|
25
|
-
# @
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
@options = options
|
10
|
+
# @return [Installer] The installer instance
|
11
|
+
def initialize(options = {})
|
12
|
+
@identifier = Identifier.new Dir.new(Dir.pwd), options
|
13
|
+
@resolver = Resolver.new @identifier
|
14
|
+
self
|
31
15
|
end
|
32
16
|
|
33
|
-
|
17
|
+
Contract None => NilClass
|
18
|
+
# Installs packages
|
34
19
|
#
|
35
|
-
# @return
|
36
|
-
def cache_directory
|
37
|
-
@options[:cache_directory]
|
38
|
-
end
|
39
|
-
|
40
|
-
# Executes the installation
|
41
|
-
#
|
42
|
-
# :reek:TooManyStatements { max_statements: 7 }
|
43
|
-
#
|
44
|
-
# @return [void]
|
20
|
+
# @return nil
|
45
21
|
def install
|
46
22
|
puts 'Resolving packages...'
|
47
|
-
resolver.
|
23
|
+
@graph = @resolver.resolve
|
48
24
|
|
49
25
|
puts 'Solving dependencies...'
|
50
|
-
|
51
|
-
rescue
|
52
|
-
retry_install
|
53
|
-
end
|
54
|
-
|
55
|
-
# Saves the caches
|
56
|
-
#
|
57
|
-
# @return [void]
|
58
|
-
def save
|
59
|
-
puts 'Saving package cache...'
|
60
|
-
@git_resolver.save
|
61
|
-
@cache.save
|
62
|
-
end
|
26
|
+
(Populator.new results).populate
|
63
27
|
|
64
|
-
# Clears the reference cache and retries installation.
|
65
|
-
#
|
66
|
-
# @return [void]
|
67
|
-
def retry_install
|
68
|
-
Logger.arrow(
|
69
|
-
'Could not find a solution in local cache, refreshing packages...'
|
70
|
-
)
|
71
|
-
|
72
|
-
@git_resolver.clear
|
73
|
-
resolver.add_constraints dependencies
|
74
|
-
|
75
|
-
populate_elm_stuff
|
76
|
-
rescue Solve::Errors::NoSolutionError => error
|
77
|
-
puts 'Could not find a solution:'
|
78
|
-
puts error.to_s.indent(2)
|
79
|
-
end
|
80
|
-
|
81
|
-
private
|
82
|
-
|
83
|
-
# Populates the `elm-stuff` directory with the packages from
|
84
|
-
# the solution.
|
85
|
-
#
|
86
|
-
# @return [void]
|
87
|
-
def populate_elm_stuff
|
88
|
-
save
|
89
|
-
@populator.populate calculate_solution
|
90
28
|
puts 'Packages configured successfully!'
|
29
|
+
nil
|
30
|
+
rescue Solve::Errors::NoSolutionError => error
|
31
|
+
Logger.arrow "No solution found: #{error}"
|
91
32
|
end
|
92
33
|
|
93
|
-
|
94
|
-
#
|
95
|
-
# @return [Resolver] The resolver
|
96
|
-
def resolver
|
97
|
-
@resolver ||= Resolver.new @cache, @git_resolver
|
98
|
-
end
|
99
|
-
|
100
|
-
# Returns the solution for the given `elm-package.json` file.
|
34
|
+
Contract None => ArrayOf[Dependency]
|
35
|
+
# Returns the results of solving
|
101
36
|
#
|
102
|
-
# @return [
|
103
|
-
def
|
104
|
-
Solve
|
105
|
-
|
106
|
-
|
107
|
-
|
37
|
+
# @return [Array] Array of dependencies
|
38
|
+
def results
|
39
|
+
Solve
|
40
|
+
.it!(@graph, initial_solve_constraints)
|
41
|
+
.map do |name, version|
|
42
|
+
dep = @resolver.dependencies[name]
|
43
|
+
dep.version = Semverse::Version.new(version)
|
44
|
+
dep
|
45
|
+
end
|
108
46
|
end
|
109
47
|
|
110
|
-
|
48
|
+
Contract None => Array
|
49
|
+
# Returns the inital constraints
|
111
50
|
#
|
112
|
-
# @return [
|
113
|
-
def
|
114
|
-
@
|
115
|
-
|
51
|
+
# @return [Array] Array of dependency names and constraints
|
52
|
+
def initial_solve_constraints
|
53
|
+
@identifier.initial_dependencies.flat_map do |dependency|
|
54
|
+
dependency.constraints.map do |constraint|
|
55
|
+
[dependency.name, constraint]
|
56
|
+
end
|
57
|
+
end
|
116
58
|
end
|
117
59
|
end
|
118
60
|
end
|