rbs 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,85 @@
1
+ module RBS
2
+ module Collection
3
+
4
+ # This class represent the configration file.
5
+ class Config
6
+ class CollectionNotAvailable < StandardError
7
+ def initialize
8
+ super <<~MSG
9
+ rbs collection is not initialized.
10
+ Run `rbs collection install` to install RBSs from collection.
11
+ MSG
12
+ end
13
+ end
14
+
15
+ PATH = Pathname('rbs_collection.yaml')
16
+
17
+ # Generate a rbs lockfile from Gemfile.lock to `config_path`.
18
+ # If `with_lockfile` is true, it respects existing rbs lockfile.
19
+ def self.generate_lockfile(config_path:, gemfile_lock_path:, with_lockfile: true)
20
+ LockfileGenerator.generate(config_path: config_path, gemfile_lock_path: gemfile_lock_path, with_lockfile: with_lockfile)
21
+ end
22
+
23
+ def self.from_path(path)
24
+ new(YAML.load(path.read), config_path: path)
25
+ end
26
+
27
+ def self.lockfile_of(config_path)
28
+ lock_path = to_lockfile_path(config_path)
29
+ from_path lock_path if lock_path.exist?
30
+ end
31
+
32
+ def self.to_lockfile_path(config_path)
33
+ config_path.sub_ext('.lock' + config_path.extname)
34
+ end
35
+
36
+ def initialize(data, config_path:)
37
+ @data = data
38
+ @config_path = config_path
39
+ end
40
+
41
+ def add_gem(gem)
42
+ gems << gem
43
+ end
44
+
45
+ def gem(gem_name)
46
+ gems.find { |gem| gem['name'] == gem_name }
47
+ end
48
+
49
+ def repo_path
50
+ @config_path.dirname.join @data['path']
51
+ end
52
+
53
+ def sources
54
+ @sources ||= (
55
+ @data['sources']
56
+ .map { |c| Sources.from_config_entry(c) }
57
+ .push(Sources::Stdlib.instance)
58
+ .push(Sources::Rubygems.instance)
59
+ )
60
+ end
61
+
62
+ def dump_to(io)
63
+ YAML.dump(@data, io)
64
+ end
65
+
66
+ def gems
67
+ @data['gems'] ||= []
68
+ end
69
+
70
+ # It raises an error when there are non-available libraries
71
+ def check_rbs_availability!
72
+ raise CollectionNotAvailable unless repo_path.exist?
73
+
74
+ gems.each do |gem|
75
+ case gem['source']['type']
76
+ when 'git'
77
+ meta_path = repo_path.join(gem['name'], gem['version'], Sources::Git::METADATA_FILENAME)
78
+ raise CollectionNotAvailable unless meta_path.exist?
79
+ raise CollectionNotAvailable unless gem == YAML.load(meta_path.read)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,27 @@
1
+ module RBS
2
+ module Collection
3
+ class Installer
4
+ attr_reader :lockfile
5
+ attr_reader :stdout
6
+
7
+ def initialize(lockfile_path:, stdout: $stdout)
8
+ @lockfile = Config.from_path(lockfile_path)
9
+ @stdout = stdout
10
+ end
11
+
12
+ def install_from_lockfile
13
+ install_to = lockfile.repo_path
14
+ lockfile.gems.each do |config_entry|
15
+ source_for(config_entry).install(dest: install_to, config_entry: config_entry, stdout: stdout)
16
+ end
17
+ stdout.puts "It's done! #{lockfile.gems.size} gems' RBSs now installed."
18
+ end
19
+
20
+ private def source_for(config_entry)
21
+ @source_for ||= {}
22
+ key = config_entry['source'] or raise
23
+ @source_for[key] ||= Sources.from_config_entry(key)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,147 @@
1
+ require 'digest/sha2'
2
+ require 'open3'
3
+
4
+ module RBS
5
+ module Collection
6
+ module Sources
7
+ class Git
8
+ METADATA_FILENAME = '.rbs_meta.yaml'
9
+
10
+ class CommandError < StandardError; end
11
+
12
+ attr_reader :name, :remote, :repo_dir
13
+
14
+ def initialize(name:, revision:, remote:, repo_dir:)
15
+ @name = name
16
+ @remote = remote
17
+ @repo_dir = repo_dir || 'gems'
18
+
19
+ setup!(revision: revision)
20
+ end
21
+
22
+ def has?(config_entry)
23
+ gem_name = config_entry['name']
24
+ gem_repo_dir.join(gem_name).directory?
25
+ end
26
+
27
+ def versions(config_entry)
28
+ gem_name = config_entry['name']
29
+ gem_repo_dir.join(gem_name).glob('*/').map { |path| path.basename.to_s }
30
+ end
31
+
32
+ def install(dest:, config_entry:, stdout:)
33
+ gem_name = config_entry['name']
34
+ version = config_entry['version'] or raise
35
+ gem_dir = dest.join(gem_name, version)
36
+
37
+ if gem_dir.directory?
38
+ if (prev = YAML.load_file(gem_dir.join(METADATA_FILENAME))) == config_entry
39
+ stdout.puts "Using #{format_config_entry(config_entry)}"
40
+ else
41
+ # @type var prev: RBS::Collection::Config::gem_entry
42
+ stdout.puts "Updating to #{format_config_entry(config_entry)} from #{format_config_entry(prev)}"
43
+ FileUtils.remove_entry_secure(gem_dir.to_s)
44
+ _install(dest: dest, config_entry: config_entry)
45
+ end
46
+ else
47
+ stdout.puts "Installing #{format_config_entry(config_entry)}"
48
+ _install(dest: dest, config_entry: config_entry)
49
+ end
50
+ end
51
+
52
+ private def _install(dest:, config_entry:)
53
+ gem_name = config_entry['name']
54
+ version = config_entry['version'] or raise
55
+ dest = dest.join(gem_name)
56
+ dest.mkpath
57
+ src = gem_repo_dir.join(gem_name, version)
58
+
59
+ FileUtils.cp_r(src, dest)
60
+ dest.join(version, METADATA_FILENAME).write(YAML.dump(config_entry))
61
+ end
62
+
63
+ def to_lockfile
64
+ {
65
+ 'type' => 'git',
66
+ 'name' => name,
67
+ 'revision' => resolved_revision,
68
+ 'remote' => remote,
69
+ 'repo_dir' => repo_dir,
70
+ }
71
+ end
72
+
73
+ private def format_config_entry(config_entry)
74
+ name = config_entry['name']
75
+ v = config_entry['version']
76
+
77
+ rev = resolved_revision[0..10]
78
+ desc = "#{name}@#{rev}"
79
+
80
+ "#{name}:#{v} (#{desc})"
81
+ end
82
+
83
+ private def setup!(revision:)
84
+ git_dir.mkpath
85
+ if git_dir.join('.git').directory?
86
+ if need_to_fetch?(revision)
87
+ git 'fetch', 'origin'
88
+ end
89
+ else
90
+ git 'clone', remote, git_dir.to_s
91
+ end
92
+
93
+ begin
94
+ git 'checkout', "origin/#{revision}" # for branch name as `revision`
95
+ rescue CommandError
96
+ git 'checkout', revision
97
+ end
98
+ end
99
+
100
+ private def need_to_fetch?(revision)
101
+ return true unless revision.match?(/\A[a-f0-9]{40}\z/)
102
+
103
+ begin
104
+ git('cat-file', '-e', revision)
105
+ false
106
+ rescue CommandError
107
+ true
108
+ end
109
+ end
110
+
111
+ private def git_dir
112
+ @git_dir ||= (
113
+ base = Pathname(ENV['XDG_CACHE_HOME'] || File.expand_path("~/.cache"))
114
+ dir = base.join('rbs', Digest::SHA256.hexdigest(remote))
115
+ dir.mkpath
116
+ dir
117
+ )
118
+ end
119
+
120
+ private def gem_repo_dir
121
+ git_dir.join @repo_dir
122
+ end
123
+
124
+ private def resolved_revision
125
+ @resolved_revision ||= resolve_revision
126
+ end
127
+
128
+ private def resolve_revision
129
+ git('rev-parse', 'HEAD').chomp
130
+ end
131
+
132
+ private def git(*cmd)
133
+ sh! 'git', *cmd
134
+ end
135
+
136
+ private def sh!(*cmd)
137
+ RBS.logger.debug "$ #{cmd.join(' ')}"
138
+ (__skip__ = Open3.capture3(*cmd, chdir: git_dir)).then do |out, err, status|
139
+ raise CommandError, "Unexpected status #{status.exitstatus}\n\n#{err}" unless status.success?
140
+
141
+ out
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,40 @@
1
+ require 'singleton'
2
+
3
+ module RBS
4
+ module Collection
5
+ module Sources
6
+ # Signatures that are inclduded in gem package as sig/ directory.
7
+ class Rubygems
8
+ include Singleton
9
+
10
+ def has?(config_entry)
11
+ gem_sig_path(config_entry)
12
+ end
13
+
14
+ def versions(config_entry)
15
+ spec, _ = gem_sig_path(config_entry)
16
+ spec or raise
17
+ [spec.version.to_s]
18
+ end
19
+
20
+ def install(dest:, config_entry:, stdout:)
21
+ # Do nothing because stdlib RBS is available by default
22
+ name = config_entry['name']
23
+ version = config_entry['version'] or raise
24
+ _, from = gem_sig_path(config_entry)
25
+ stdout.puts "Using #{name}:#{version} (#{from})"
26
+ end
27
+
28
+ def to_lockfile
29
+ {
30
+ 'type' => 'rubygems',
31
+ }
32
+ end
33
+
34
+ private def gem_sig_path(config_entry)
35
+ RBS::EnvironmentLoader.gem_sig_path(config_entry['name'], config_entry['version'])
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,38 @@
1
+ require 'singleton'
2
+
3
+ module RBS
4
+ module Collection
5
+ module Sources
6
+ # signatures that are bundled in rbs gem under the stdlib/ directory
7
+ class Stdlib
8
+ include Singleton
9
+
10
+ def has?(config_entry)
11
+ gem_dir(config_entry).exist?
12
+ end
13
+
14
+ def versions(config_entry)
15
+ gem_dir(config_entry).glob('*/').map { |path| path.basename.to_s }
16
+ end
17
+
18
+ def install(dest:, config_entry:, stdout:)
19
+ # Do nothing because stdlib RBS is available by default
20
+ name = config_entry['name']
21
+ version = config_entry['version'] or raise
22
+ from = gem_dir(config_entry) / version
23
+ stdout.puts "Using #{name}:#{version} (#{from})"
24
+ end
25
+
26
+ def to_lockfile
27
+ {
28
+ 'type' => 'stdlib',
29
+ }
30
+ end
31
+
32
+ private def gem_dir(config_entry)
33
+ Repository::DEFAULT_STDLIB_ROOT.join(config_entry['name'])
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ require_relative './sources/git'
2
+ require_relative './sources/stdlib'
3
+ require_relative './sources/rubygems'
4
+
5
+ module RBS
6
+ module Collection
7
+ module Sources
8
+ def self.from_config_entry(source_entry)
9
+ case source_entry['type']
10
+ when 'git', nil # git source by default
11
+ __skip__ = Git.new(**source_entry.slice('name', 'revision', 'remote', 'repo_dir').transform_keys(&:to_sym))
12
+ when 'stdlib'
13
+ Stdlib.instance
14
+ when 'rubygems'
15
+ Rubygems.instance
16
+ else
17
+ raise
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ require 'yaml'
2
+ require 'bundler'
3
+
4
+ require_relative './collection/sources'
5
+ require_relative './collection/config'
6
+ require_relative './collection/config/lockfile_generator'
7
+ require_relative './collection/installer'
8
+ require_relative './collection/cleaner'
9
+
10
+ module RBS
11
+ module Collection
12
+ end
13
+ end
@@ -47,6 +47,18 @@ module RBS
47
47
  end
48
48
  end
49
49
 
50
+ def add_collection(collection_config)
51
+ warn "warning: rbs collection is experimental, and the behavior may change until RBS v2.0"
52
+
53
+ collection_config.check_rbs_availability!
54
+
55
+ repository.add(collection_config.repo_path)
56
+
57
+ collection_config.gems.each do |gem|
58
+ add(library: gem['name'], version: gem['version'])
59
+ end
60
+ end
61
+
50
62
  def has_library?(library:, version:)
51
63
  if self.class.gem_sig_path(library, version) || repository.lookup(library, version)
52
64
  true
data/lib/rbs/errors.rb CHANGED
@@ -383,6 +383,8 @@ module RBS
383
383
  "include"
384
384
  when AST::Members::Extend
385
385
  "extend"
386
+ else
387
+ raise
386
388
  end
387
389
  end
388
390
  end
@@ -55,13 +55,8 @@ module RBS
55
55
  end
56
56
 
57
57
  def find_best_version(version)
58
- return latest_version unless version
59
-
60
- if v = version_names.reverse.bsearch {|v| v <= version ? true : false }
61
- versions[v]
62
- else
63
- oldest_version
64
- end
58
+ best_version = Repository.find_best_version(version, version_names)
59
+ versions[best_version]
65
60
  end
66
61
 
67
62
  def empty?
@@ -89,6 +84,17 @@ module RBS
89
84
  end
90
85
  end
91
86
 
87
+ def self.find_best_version(version, candidates)
88
+ candidates = candidates.sort
89
+ return candidates.last || raise unless version
90
+
91
+ if v = candidates.reverse.bsearch {|v| v <= version ? true : false }
92
+ v
93
+ else
94
+ candidates.first or raise
95
+ end
96
+ end
97
+
92
98
  def add(dir)
93
99
  dirs << dir
94
100