rbs 1.5.0 → 1.6.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.
@@ -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