yarbs 0.1.1 → 0.2.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/README.md +73 -5
- data/lib/yarbs/cli/init.rb +203 -0
- data/lib/yarbs/cli.rb +53 -4
- data/lib/yarbs/config.rb +90 -0
- data/lib/yarbs/version.rb +4 -1
- data/lib/yarbs/watcher.rb +18 -13
- data/lib/yarbs.rb +1 -0
- data/sig/yarbs/annotator.rbs +11 -0
- data/sig/yarbs/cli/init.rbs +76 -0
- data/sig/yarbs/cli.rbs +6 -0
- data/sig/yarbs/config.rbs +54 -0
- data/sig/yarbs/type_converter.rbs +15 -0
- data/sig/yarbs/version.rbs +4 -1
- data/sig/yarbs/watcher.rbs +9 -4
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 601e10f12020d139a62bd8655f95b6c56bb5037d012265abc5d7adddfda0d60d
|
|
4
|
+
data.tar.gz: 34d6a692e0d7323d3fba7407b9586c1ddccdcba755c3333673cb520d28b6d762
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 947060cd7c782f1e3280a12f4ea8e01eb47460d3820c58bcbfd87c6a780907e8d28b5958d2b827bc0d9987ec9f84b9a6010da57310cc7d1878164972eba8b1ee
|
|
7
|
+
data.tar.gz: fa4fc3e6a0e9d4f2a78d5762aeb841426b0a7bbf23a8d4de60c75ffd6014891aa87868ee3eccb22d6dedbc42ab8e5099b9c00885959bc7580c2efcefe1967477
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Yarbs
|
|
2
2
|
|
|
3
|
-
[](https://github.com/tobidelius/yarbs/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
5
|
*This is still experimental.*
|
|
6
6
|
|
|
@@ -58,14 +58,62 @@ executing:
|
|
|
58
58
|
gem install yarbs
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
## Getting started
|
|
62
|
+
|
|
63
|
+
In your project, run:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bundle exec yarbs init
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
create .yarbs.yml
|
|
71
|
+
create Steepfile
|
|
72
|
+
create rbs_collection.yaml
|
|
73
|
+
update .gitignore (added /.gem_rbs_collection/)
|
|
74
|
+
run rbs collection install
|
|
75
|
+
wrote 23 files to sig/
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This sets up everything needed to type-check against the generated
|
|
79
|
+
signatures:
|
|
80
|
+
|
|
81
|
+
- `.yarbs.yml` — which files yarbs reads and where it writes (see
|
|
82
|
+
[Configuration](#configuration))
|
|
83
|
+
- `Steepfile` — a Steep target that checks your sources against `sig/`,
|
|
84
|
+
with lenient diagnostics to start with, since most code stays `untyped`
|
|
85
|
+
until it's documented
|
|
86
|
+
- `rbs_collection.yaml` — so Steep gets signatures for the gems you
|
|
87
|
+
depend on; `init` then runs `rbs collection install` to fetch them (skip
|
|
88
|
+
that with `--no-install`)
|
|
89
|
+
- `.gitignore` — ignores the downloaded `.gem_rbs_collection/`, and, for a
|
|
90
|
+
gem (a project with a `*.gemspec`), `rbs_collection.lock.yaml` too; an app
|
|
91
|
+
should commit that lock file, just like `Gemfile.lock`
|
|
92
|
+
|
|
93
|
+
Then it generates your signatures for the first time.
|
|
94
|
+
|
|
95
|
+
It's safe to re-run: `init` never overwrites a file that already exists (it
|
|
96
|
+
reports it as skipped), and only adds the `.gitignore` entries that are
|
|
97
|
+
missing. Use `--dry-run` to see what it would do first. By default it sets
|
|
98
|
+
yarbs up for `lib/**/*.rb`; pass your own globs to change that
|
|
99
|
+
(`yarbs init "lib/**/*.rb" "src/**/*.rb"`).
|
|
100
|
+
|
|
101
|
+
If Steep isn't in your bundle yet, `init` finishes by telling you to add it:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
bundle add steep --group development
|
|
105
|
+
bundle exec steep check
|
|
106
|
+
```
|
|
107
|
+
|
|
61
108
|
## Usage
|
|
62
109
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
110
|
+
Run `yarbs` to regenerate your signatures. It writes one `.rbs` file per
|
|
111
|
+
source file into `sig/`, mirroring the source layout with a leading
|
|
112
|
+
`lib/`/`app/` stripped. Without arguments it uses the paths in
|
|
113
|
+
`.yarbs.yml`; give it glob patterns to override them:
|
|
67
114
|
|
|
68
115
|
```bash
|
|
116
|
+
yarbs
|
|
69
117
|
yarbs "lib/**/*.rb" "app/**/*.rb"
|
|
70
118
|
```
|
|
71
119
|
|
|
@@ -88,6 +136,26 @@ require "yarbs"
|
|
|
88
136
|
Yarbs.generate(["lib/**/*.rb"], output_dir: "sig", strict: true)
|
|
89
137
|
```
|
|
90
138
|
|
|
139
|
+
### Configuration
|
|
140
|
+
|
|
141
|
+
`.yarbs.yml` (written by `yarbs init`) holds the defaults for a plain
|
|
142
|
+
`yarbs` run. Anything passed on the command line overrides it:
|
|
143
|
+
|
|
144
|
+
```yaml
|
|
145
|
+
# Glob patterns for the Ruby files to generate signatures for.
|
|
146
|
+
paths:
|
|
147
|
+
- "lib/**/*.rb"
|
|
148
|
+
|
|
149
|
+
# Directory the generated .rbs files are written into.
|
|
150
|
+
output: sig
|
|
151
|
+
|
|
152
|
+
# Raise instead of falling back to `untyped` when a YARD type can't
|
|
153
|
+
# be converted. Ignored by `yarbs --watch`.
|
|
154
|
+
strict: false
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Without a `.yarbs.yml`, `yarbs` uses these same defaults.
|
|
158
|
+
|
|
91
159
|
### Docs
|
|
92
160
|
|
|
93
161
|
For the full picture of what yarbs understands and how to document it —
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler"
|
|
4
|
+
require "rbs"
|
|
5
|
+
require "rbs/cli"
|
|
6
|
+
require "stringio"
|
|
7
|
+
|
|
8
|
+
module Yarbs
|
|
9
|
+
module CLI
|
|
10
|
+
# `yarbs init`: sets a project up to use yarbs with Steep, so the
|
|
11
|
+
# signatures it writes are actually checked.
|
|
12
|
+
#
|
|
13
|
+
# Every step is safe to re-run: files that already exist are left alone
|
|
14
|
+
# (and reported as skipped), and `.gitignore` only gets the entries it's
|
|
15
|
+
# missing.
|
|
16
|
+
class Init
|
|
17
|
+
STEEPFILE = "Steepfile"
|
|
18
|
+
RBS_COLLECTION = "rbs_collection.yaml"
|
|
19
|
+
GITIGNORE = ".gitignore"
|
|
20
|
+
GEM_RBS_COLLECTION_DIR = "/.gem_rbs_collection/"
|
|
21
|
+
RBS_COLLECTION_LOCK = "/rbs_collection.lock.yaml"
|
|
22
|
+
|
|
23
|
+
# Runs every setup step in the current directory.
|
|
24
|
+
#
|
|
25
|
+
# @param paths [Array<String>, nil] glob patterns to write into a new
|
|
26
|
+
# .yarbs.yml (defaults to +lib/**/*.rb+)
|
|
27
|
+
# @param dry_run [Boolean] report what would change without writing anything
|
|
28
|
+
# @param install [Boolean] run +rbs collection install+ afterwards
|
|
29
|
+
# @param out [IO] where to report each step
|
|
30
|
+
# @return [void]
|
|
31
|
+
def self.run(paths: nil, dry_run: false, install: true, out: $stdout)
|
|
32
|
+
new(paths, dry_run, install, out).run
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize(paths, dry_run, install, out)
|
|
36
|
+
@paths = paths
|
|
37
|
+
@dry_run = dry_run
|
|
38
|
+
@install = install
|
|
39
|
+
@out = out
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def run
|
|
43
|
+
report "dry run", "nothing will be written" if @dry_run
|
|
44
|
+
|
|
45
|
+
config = write_config
|
|
46
|
+
create_file(STEEPFILE, steepfile(config))
|
|
47
|
+
create_file(RBS_COLLECTION, rbs_collection)
|
|
48
|
+
update_gitignore
|
|
49
|
+
install_collection
|
|
50
|
+
generate(config)
|
|
51
|
+
|
|
52
|
+
return if Gem::Specification.find_all_by_name("steep").any?
|
|
53
|
+
|
|
54
|
+
report "next", "add Steep to type-check against the signatures: bundle add steep --group development"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# An existing .yarbs.yml wins over any paths given to `init`, so the
|
|
60
|
+
# Steepfile always matches what `yarbs` will actually generate.
|
|
61
|
+
def write_config
|
|
62
|
+
if File.exist?(Config::FILE)
|
|
63
|
+
report "skip", "#{Config::FILE} (already exists)"
|
|
64
|
+
return Config.load
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
config = @paths ? Config.new(paths: @paths) : Config.new
|
|
68
|
+
create_file(Config::FILE, config.to_yaml)
|
|
69
|
+
config
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @param config [Config]
|
|
73
|
+
# @return [String]
|
|
74
|
+
def steepfile(config)
|
|
75
|
+
checks = config.paths.map { |pattern| Watcher.base_directory(pattern) }.uniq
|
|
76
|
+
|
|
77
|
+
<<~RUBY
|
|
78
|
+
D = Steep::Diagnostic
|
|
79
|
+
|
|
80
|
+
target :app do
|
|
81
|
+
signature #{config.output.inspect}
|
|
82
|
+
|
|
83
|
+
#{checks.map { |dir| "check #{dir.inspect}" }.join("\n ")}
|
|
84
|
+
|
|
85
|
+
# Lenient to start with, since most of a codebase is `untyped` until
|
|
86
|
+
# it's documented. Tighten this (D::Ruby.default, D::Ruby.strict)
|
|
87
|
+
# as more of it gets YARD types.
|
|
88
|
+
configure_code_diagnostics(D::Ruby.lenient)
|
|
89
|
+
end
|
|
90
|
+
RUBY
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @return [String]
|
|
94
|
+
def rbs_collection
|
|
95
|
+
<<~YAML
|
|
96
|
+
# Where Steep gets type signatures for your gems from.
|
|
97
|
+
# Run `rbs collection install` after changing your Gemfile.
|
|
98
|
+
sources:
|
|
99
|
+
- type: git
|
|
100
|
+
name: ruby/gem_rbs_collection
|
|
101
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
102
|
+
revision: main
|
|
103
|
+
repo_dir: gems
|
|
104
|
+
|
|
105
|
+
# A directory to install the downloaded RBSs
|
|
106
|
+
path: .gem_rbs_collection
|
|
107
|
+
|
|
108
|
+
# gems:
|
|
109
|
+
# # If you want to avoid installing rbs files for gems, you can specify them here.
|
|
110
|
+
# - name: GEM_NAME
|
|
111
|
+
# ignore: true
|
|
112
|
+
YAML
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def create_file(path, contents)
|
|
116
|
+
if File.exist?(path)
|
|
117
|
+
report "skip", "#{path} (already exists)"
|
|
118
|
+
return
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
File.write(path, contents) unless @dry_run
|
|
122
|
+
report "create", path
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# A gem's rbs_collection.lock.yaml is conventionally not committed (its
|
|
126
|
+
# users resolve their own), but an app should commit it, just like its
|
|
127
|
+
# Gemfile.lock.
|
|
128
|
+
def gitignore_entries
|
|
129
|
+
entries = [GEM_RBS_COLLECTION_DIR]
|
|
130
|
+
entries << RBS_COLLECTION_LOCK if Dir.glob("*.gemspec").any?
|
|
131
|
+
entries
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def update_gitignore
|
|
135
|
+
exists = File.exist?(GITIGNORE)
|
|
136
|
+
unless exists || Dir.exist?(".git")
|
|
137
|
+
report "skip", "#{GITIGNORE} (not a git repository)"
|
|
138
|
+
return
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
contents = exists ? File.read(GITIGNORE) : ""
|
|
142
|
+
present = contents.lines.map { |line| normalize_ignore(line) }
|
|
143
|
+
missing = gitignore_entries.reject { |entry| present.include?(normalize_ignore(entry)) }
|
|
144
|
+
|
|
145
|
+
if missing.empty?
|
|
146
|
+
report "skip", "#{GITIGNORE} (already has yarbs' entries)"
|
|
147
|
+
return
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
unless @dry_run
|
|
151
|
+
separator = (contents.empty? || contents.end_with?("\n")) ? "" : "\n"
|
|
152
|
+
File.write(GITIGNORE, contents + separator + missing.join("\n") + "\n")
|
|
153
|
+
end
|
|
154
|
+
report exists ? "update" : "create", "#{GITIGNORE} (added #{missing.join(", ")})"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# `/.gem_rbs_collection/`, `.gem_rbs_collection/` and
|
|
158
|
+
# `.gem_rbs_collection` all ignore the same thing at the project root,
|
|
159
|
+
# so compare them without the leading/trailing slash.
|
|
160
|
+
def normalize_ignore(line)
|
|
161
|
+
line.strip.delete_prefix("/").delete_suffix("/")
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def install_collection
|
|
165
|
+
return unless @install
|
|
166
|
+
|
|
167
|
+
unless File.exist?("Gemfile")
|
|
168
|
+
report "skip", "rbs collection install (no Gemfile)"
|
|
169
|
+
return
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
if @dry_run
|
|
173
|
+
report "run", "rbs collection install"
|
|
174
|
+
return
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
report "run", "rbs collection install"
|
|
178
|
+
output = StringIO.new
|
|
179
|
+
RBS::CLI.new(stdout: output, stderr: output).run(%w[collection install])
|
|
180
|
+
rescue StandardError, SystemExit => e
|
|
181
|
+
report "failed", "rbs collection install (#{e.message}); run it yourself once that's fixed"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def generate(config)
|
|
185
|
+
if @dry_run
|
|
186
|
+
report "run", "yarbs #{config.paths.join(" ")} -o #{config.output}"
|
|
187
|
+
return
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
written = Yarbs.generate(config.paths, output_dir: config.output, strict: config.strict)
|
|
191
|
+
if written.empty?
|
|
192
|
+
report "skip", "generating signatures (no files matched #{config.paths.join(" ")})"
|
|
193
|
+
else
|
|
194
|
+
report "wrote", "#{written.size} file#{"s" unless written.size == 1} to #{config.output}/"
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def report(action, message)
|
|
199
|
+
@out.puts "#{action.rjust(8)} #{message}"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
data/lib/yarbs/cli.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "optparse"
|
|
4
|
+
require_relative "cli/init"
|
|
4
5
|
|
|
5
6
|
module Yarbs
|
|
6
7
|
# The `yarbs` command line executable.
|
|
@@ -10,12 +11,19 @@ module Yarbs
|
|
|
10
11
|
# @param argv [Array<String>]
|
|
11
12
|
# @return [void]
|
|
12
13
|
def self.run(argv)
|
|
13
|
-
|
|
14
|
+
return init(argv.drop(1)) if argv.first == "init"
|
|
15
|
+
|
|
16
|
+
output_dir = nil
|
|
14
17
|
watch = false
|
|
15
18
|
strict = false
|
|
16
19
|
|
|
17
20
|
parser = OptionParser.new do |opts|
|
|
18
|
-
opts.banner =
|
|
21
|
+
opts.banner = <<~BANNER
|
|
22
|
+
Usage: yarbs [options] [GLOB ...]
|
|
23
|
+
yarbs init [options] [GLOB ...]
|
|
24
|
+
|
|
25
|
+
With no GLOB, uses the paths in #{Config::FILE} (or lib/**/*.rb).
|
|
26
|
+
BANNER
|
|
19
27
|
opts.on("-o", "--output DIR", "Directory to write .rbs files into (default: sig)") do |dir|
|
|
20
28
|
output_dir = dir
|
|
21
29
|
end
|
|
@@ -29,10 +37,17 @@ module Yarbs
|
|
|
29
37
|
|
|
30
38
|
patterns = parser.parse(argv)
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
config = begin
|
|
41
|
+
Config.load
|
|
42
|
+
rescue Error => e
|
|
43
|
+
warn e.message
|
|
34
44
|
exit 1
|
|
35
45
|
end
|
|
46
|
+
patterns = config.paths if patterns.empty?
|
|
47
|
+
output_dir ||= config.output
|
|
48
|
+
# The config's `strict` is ignored in watch mode rather than rejected,
|
|
49
|
+
# so a project can set it for one-off runs and still use --watch.
|
|
50
|
+
strict ||= config.strict unless watch
|
|
36
51
|
|
|
37
52
|
if strict && watch
|
|
38
53
|
warn "yarbs: --strict cannot be combined with --watch"
|
|
@@ -55,5 +70,39 @@ module Yarbs
|
|
|
55
70
|
|
|
56
71
|
written.each { |path| puts "wrote #{path}" }
|
|
57
72
|
end
|
|
73
|
+
|
|
74
|
+
# Runs `yarbs init`.
|
|
75
|
+
#
|
|
76
|
+
# @param argv [Array<String>] the arguments after `init`
|
|
77
|
+
# @return [void]
|
|
78
|
+
def self.init(argv)
|
|
79
|
+
dry_run = false
|
|
80
|
+
install = true
|
|
81
|
+
|
|
82
|
+
parser = OptionParser.new do |opts|
|
|
83
|
+
opts.banner = <<~BANNER
|
|
84
|
+
Usage: yarbs init [options] [GLOB ...]
|
|
85
|
+
|
|
86
|
+
Sets up #{Config::FILE}, a Steepfile, rbs_collection.yaml and .gitignore
|
|
87
|
+
entries, then generates signatures. Existing files are never overwritten.
|
|
88
|
+
GLOB defaults to lib/**/*.rb.
|
|
89
|
+
BANNER
|
|
90
|
+
opts.on("-n", "--dry-run", "Show what would be done without writing anything") do
|
|
91
|
+
dry_run = true
|
|
92
|
+
end
|
|
93
|
+
opts.on("--no-install", "Don't run `rbs collection install`") do
|
|
94
|
+
install = false
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
paths = parser.parse(argv)
|
|
99
|
+
|
|
100
|
+
begin
|
|
101
|
+
Init.run(paths: paths.empty? ? nil : paths, dry_run: dry_run, install: install)
|
|
102
|
+
rescue Error => e
|
|
103
|
+
warn e.message
|
|
104
|
+
exit 1
|
|
105
|
+
end
|
|
106
|
+
end
|
|
58
107
|
end
|
|
59
108
|
end
|
data/lib/yarbs/config.rb
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Yarbs
|
|
6
|
+
# The settings in a project's +.yarbs.yml+, so that a plain +yarbs+ (or
|
|
7
|
+
# +yarbs --watch+) knows what to generate without repeating the globs on
|
|
8
|
+
# every run. Written by +yarbs init+; anything given on the command line
|
|
9
|
+
# overrides it.
|
|
10
|
+
class Config
|
|
11
|
+
# The config file yarbs looks for in the current directory.
|
|
12
|
+
FILE = ".yarbs.yml"
|
|
13
|
+
|
|
14
|
+
DEFAULT_PATHS = ["lib/**/*.rb"].freeze
|
|
15
|
+
DEFAULT_OUTPUT = "sig"
|
|
16
|
+
KEYS = %w[paths output strict].freeze
|
|
17
|
+
|
|
18
|
+
# @return [Array<String>] glob patterns for Ruby source files
|
|
19
|
+
attr_reader :paths
|
|
20
|
+
|
|
21
|
+
# @return [String] directory to write generated .rbs files into
|
|
22
|
+
attr_reader :output
|
|
23
|
+
|
|
24
|
+
# @return [Boolean] raise instead of falling back to +untyped+
|
|
25
|
+
attr_reader :strict
|
|
26
|
+
|
|
27
|
+
# Loads the config file, or the defaults if there isn't one.
|
|
28
|
+
#
|
|
29
|
+
# @param path [String] the config file to read
|
|
30
|
+
# @return [Config]
|
|
31
|
+
# @raise [Error] if the file isn't valid YAML or has an unknown or
|
|
32
|
+
# mistyped setting
|
|
33
|
+
def self.load(path = FILE)
|
|
34
|
+
return new unless File.exist?(path)
|
|
35
|
+
|
|
36
|
+
data = YAML.safe_load_file(path) || {}
|
|
37
|
+
raise Error, "yarbs: #{path} must be a YAML mapping" unless data.is_a?(Hash)
|
|
38
|
+
|
|
39
|
+
unknown = data.keys - KEYS
|
|
40
|
+
raise Error, "yarbs: unknown setting#{"s" unless unknown.size == 1} in #{path}: #{unknown.join(", ")}" if unknown.any?
|
|
41
|
+
|
|
42
|
+
new(
|
|
43
|
+
paths: Array(data.fetch("paths", DEFAULT_PATHS)),
|
|
44
|
+
output: data.fetch("output", DEFAULT_OUTPUT),
|
|
45
|
+
strict: data.fetch("strict", false)
|
|
46
|
+
).tap { |config| config.validate!(path) }
|
|
47
|
+
rescue Psych::Exception => e
|
|
48
|
+
raise Error, "yarbs: could not parse #{path} (#{e.message})"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param paths [Array<String>]
|
|
52
|
+
# @param output [String]
|
|
53
|
+
# @param strict [Boolean]
|
|
54
|
+
def initialize(paths: DEFAULT_PATHS, output: DEFAULT_OUTPUT, strict: false)
|
|
55
|
+
@paths = paths
|
|
56
|
+
@output = output
|
|
57
|
+
@strict = strict
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @param path [String] the config file, for the error message
|
|
61
|
+
# @return [void]
|
|
62
|
+
# @raise [Error]
|
|
63
|
+
def validate!(path)
|
|
64
|
+
raise Error, "yarbs: `paths` in #{path} must be a list of glob strings" unless paths.all?(String) && paths.any?
|
|
65
|
+
raise Error, "yarbs: `output` in #{path} must be a directory name" unless output.is_a?(String)
|
|
66
|
+
raise Error, "yarbs: `strict` in #{path} must be true or false" unless [true, false].include?(strict)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The YAML written to a new config file.
|
|
70
|
+
#
|
|
71
|
+
# @return [String]
|
|
72
|
+
def to_yaml
|
|
73
|
+
<<~YAML
|
|
74
|
+
# Settings for yarbs (https://github.com/tobidelius/yarbs).
|
|
75
|
+
# Anything passed on the command line overrides these.
|
|
76
|
+
|
|
77
|
+
# Glob patterns for the Ruby files to generate signatures for.
|
|
78
|
+
paths:
|
|
79
|
+
#{paths.map { |path| " - #{path.inspect}" }.join("\n")}
|
|
80
|
+
|
|
81
|
+
# Directory the generated .rbs files are written into.
|
|
82
|
+
output: #{output}
|
|
83
|
+
|
|
84
|
+
# Raise instead of falling back to `untyped` when a YARD type can't
|
|
85
|
+
# be converted. Ignored by `yarbs --watch`.
|
|
86
|
+
strict: #{strict}
|
|
87
|
+
YAML
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
data/lib/yarbs/version.rb
CHANGED
data/lib/yarbs/watcher.rb
CHANGED
|
@@ -18,6 +18,23 @@ module Yarbs
|
|
|
18
18
|
@output_dir = output_dir
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
# The directory a glob pattern searches under: everything before its
|
|
22
|
+
# first glob character, e.g. "lib/**/*.rb" -> "lib". Used both as the
|
|
23
|
+
# directory to hand to `Listen.to` and by `yarbs init` for the
|
|
24
|
+
# Steepfile's `check`.
|
|
25
|
+
#
|
|
26
|
+
# @param pattern [String]
|
|
27
|
+
# @return [String]
|
|
28
|
+
def self.base_directory(pattern)
|
|
29
|
+
glob_index = pattern.index(/[*?{\[]/)
|
|
30
|
+
return File.dirname(pattern) unless glob_index
|
|
31
|
+
|
|
32
|
+
prefix = pattern[0...glob_index]
|
|
33
|
+
return "." if prefix.empty?
|
|
34
|
+
|
|
35
|
+
prefix.end_with?("/") ? prefix.chomp("/") : File.dirname(prefix)
|
|
36
|
+
end
|
|
37
|
+
|
|
21
38
|
# Starts watching and blocks until interrupted (Ctrl-C).
|
|
22
39
|
#
|
|
23
40
|
# @return [void]
|
|
@@ -74,19 +91,7 @@ module Yarbs
|
|
|
74
91
|
end
|
|
75
92
|
|
|
76
93
|
def watch_directories
|
|
77
|
-
@patterns.filter_map { |pattern| base_directory(pattern) }.uniq.select { |dir| Dir.exist?(dir) }
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# The directory to hand to `Listen.to` for a glob pattern: everything
|
|
81
|
-
# before its first glob character, e.g. "lib/**/*.rb" -> "lib".
|
|
82
|
-
def base_directory(pattern)
|
|
83
|
-
glob_index = pattern.index(/[*?{\[]/)
|
|
84
|
-
return File.dirname(pattern) unless glob_index
|
|
85
|
-
|
|
86
|
-
prefix = pattern[0...glob_index]
|
|
87
|
-
return "." if prefix.empty?
|
|
88
|
-
|
|
89
|
-
prefix.end_with?("/") ? prefix.chomp("/") : File.dirname(prefix)
|
|
94
|
+
@patterns.filter_map { |pattern| self.class.base_directory(pattern) }.uniq.select { |dir| Dir.exist?(dir) }
|
|
90
95
|
end
|
|
91
96
|
end
|
|
92
97
|
end
|
data/lib/yarbs.rb
CHANGED
data/sig/yarbs/annotator.rbs
CHANGED
|
@@ -33,6 +33,17 @@ module Yarbs
|
|
|
33
33
|
|
|
34
34
|
def annotate_block: (untyped block, untyped yard_method) -> untyped
|
|
35
35
|
|
|
36
|
+
# `rbs prototype rb` marks an explicit `&block` as required unless the
|
|
37
|
+
# body checks `block_given?` or only uses the block conditionally, so a
|
|
38
|
+
# block that's merely captured or forwarded (`other(&block)`) comes out
|
|
39
|
+
# required. Treat the block as optional when:
|
|
40
|
+
#
|
|
41
|
+
# - its `@param` tag includes `nil` (`@param block [Proc, nil]`), or
|
|
42
|
+
# - the body never `yield`s (the block type is an `UntypedFunction`) and
|
|
43
|
+
# no `@yield`/`@yieldparam`/`@yieldreturn` tag documents a contract
|
|
44
|
+
# for callers to fulfil.
|
|
45
|
+
def optional_block?: (untyped block, untyped yard_method) -> bool
|
|
46
|
+
|
|
36
47
|
def annotate_block_function: (untyped function, untyped yield_params, untyped new_return_type) -> untyped
|
|
37
48
|
|
|
38
49
|
# There's no arity to preserve here, but if `@yieldparam` tags exist we
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module Yarbs
|
|
2
|
+
module CLI
|
|
3
|
+
# `yarbs init`: sets a project up to use yarbs with Steep, so the
|
|
4
|
+
# signatures it writes are actually checked.
|
|
5
|
+
#
|
|
6
|
+
# Every step is safe to re-run: files that already exist are left alone
|
|
7
|
+
# (and reported as skipped), and `.gitignore` only gets the entries it's
|
|
8
|
+
# missing.
|
|
9
|
+
class Init
|
|
10
|
+
@paths: untyped
|
|
11
|
+
|
|
12
|
+
@dry_run: untyped
|
|
13
|
+
|
|
14
|
+
@install: untyped
|
|
15
|
+
|
|
16
|
+
@out: untyped
|
|
17
|
+
|
|
18
|
+
STEEPFILE: "Steepfile"
|
|
19
|
+
|
|
20
|
+
RBS_COLLECTION: "rbs_collection.yaml"
|
|
21
|
+
|
|
22
|
+
GITIGNORE: ".gitignore"
|
|
23
|
+
|
|
24
|
+
GEM_RBS_COLLECTION_DIR: "/.gem_rbs_collection/"
|
|
25
|
+
|
|
26
|
+
RBS_COLLECTION_LOCK: "/rbs_collection.lock.yaml"
|
|
27
|
+
|
|
28
|
+
# Runs every setup step in the current directory.
|
|
29
|
+
#
|
|
30
|
+
# @param paths [Array<String>, nil] glob patterns to write into a new
|
|
31
|
+
# .yarbs.yml (defaults to +lib/**/*.rb+)
|
|
32
|
+
# @param dry_run [Boolean] report what would change without writing anything
|
|
33
|
+
# @param install [Boolean] run +rbs collection install+ afterwards
|
|
34
|
+
# @param out [IO] where to report each step
|
|
35
|
+
# @return [void]
|
|
36
|
+
def self.run: (?paths: untyped?, ?dry_run: bool, ?install: bool, ?out: untyped) -> untyped
|
|
37
|
+
|
|
38
|
+
def initialize: (untyped paths, untyped dry_run, untyped install, untyped out) -> void
|
|
39
|
+
|
|
40
|
+
def run: () -> (nil | untyped)
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# An existing .yarbs.yml wins over any paths given to `init`, so the
|
|
45
|
+
# Steepfile always matches what `yarbs` will actually generate.
|
|
46
|
+
def write_config: () -> untyped
|
|
47
|
+
|
|
48
|
+
# @param config [Config]
|
|
49
|
+
# @return [String]
|
|
50
|
+
def steepfile: (Config config) -> String
|
|
51
|
+
|
|
52
|
+
# @return [String]
|
|
53
|
+
def rbs_collection: () -> String
|
|
54
|
+
|
|
55
|
+
def create_file: (untyped path, untyped contents) -> (nil | untyped)
|
|
56
|
+
|
|
57
|
+
# A gem's rbs_collection.lock.yaml is conventionally not committed (its
|
|
58
|
+
# users resolve their own), but an app should commit it, just like its
|
|
59
|
+
# Gemfile.lock.
|
|
60
|
+
def gitignore_entries: () -> untyped
|
|
61
|
+
|
|
62
|
+
def update_gitignore: () -> (nil | untyped)
|
|
63
|
+
|
|
64
|
+
# `/.gem_rbs_collection/`, `.gem_rbs_collection/` and
|
|
65
|
+
# `.gem_rbs_collection` all ignore the same thing at the project root,
|
|
66
|
+
# so compare them without the leading/trailing slash.
|
|
67
|
+
def normalize_ignore: (untyped line) -> untyped
|
|
68
|
+
|
|
69
|
+
def install_collection: () -> untyped
|
|
70
|
+
|
|
71
|
+
def generate: (untyped config) -> (nil | untyped)
|
|
72
|
+
|
|
73
|
+
def report: (untyped action, untyped message) -> untyped
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
data/sig/yarbs/cli.rbs
CHANGED
|
@@ -6,5 +6,11 @@ module Yarbs
|
|
|
6
6
|
# @param argv [Array<String>]
|
|
7
7
|
# @return [void]
|
|
8
8
|
def self.run: (Array[String] argv) -> void
|
|
9
|
+
|
|
10
|
+
# Runs `yarbs init`.
|
|
11
|
+
#
|
|
12
|
+
# @param argv [Array<String>] the arguments after `init`
|
|
13
|
+
# @return [void]
|
|
14
|
+
def self.init: (Array[String] argv) -> void
|
|
9
15
|
end
|
|
10
16
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Yarbs
|
|
2
|
+
# The settings in a project's +.yarbs.yml+, so that a plain +yarbs+ (or
|
|
3
|
+
# +yarbs --watch+) knows what to generate without repeating the globs on
|
|
4
|
+
# every run. Written by +yarbs init+; anything given on the command line
|
|
5
|
+
# overrides it.
|
|
6
|
+
class Config
|
|
7
|
+
@paths: untyped
|
|
8
|
+
|
|
9
|
+
@output: untyped
|
|
10
|
+
|
|
11
|
+
@strict: untyped
|
|
12
|
+
|
|
13
|
+
# The config file yarbs looks for in the current directory.
|
|
14
|
+
FILE: ".yarbs.yml"
|
|
15
|
+
|
|
16
|
+
DEFAULT_PATHS: ::Array["lib/**/*.rb"]
|
|
17
|
+
|
|
18
|
+
DEFAULT_OUTPUT: "sig"
|
|
19
|
+
|
|
20
|
+
KEYS: ::Array["paths" | "output" | "strict"]
|
|
21
|
+
|
|
22
|
+
# @return [Array<String>] glob patterns for Ruby source files
|
|
23
|
+
attr_reader paths: Array[String]
|
|
24
|
+
|
|
25
|
+
# @return [String] directory to write generated .rbs files into
|
|
26
|
+
attr_reader output: String
|
|
27
|
+
|
|
28
|
+
# @return [Boolean] raise instead of falling back to +untyped+
|
|
29
|
+
attr_reader strict: bool
|
|
30
|
+
|
|
31
|
+
# Loads the config file, or the defaults if there isn't one.
|
|
32
|
+
#
|
|
33
|
+
# @param path [String] the config file to read
|
|
34
|
+
# @return [Config]
|
|
35
|
+
# @raise [Error] if the file isn't valid YAML or has an unknown or
|
|
36
|
+
# mistyped setting
|
|
37
|
+
def self.load: (?String path) -> Config
|
|
38
|
+
|
|
39
|
+
# @param paths [Array<String>]
|
|
40
|
+
# @param output [String]
|
|
41
|
+
# @param strict [Boolean]
|
|
42
|
+
def initialize: (?paths: Array[String], ?output: String, ?strict: bool) -> void
|
|
43
|
+
|
|
44
|
+
# @param path [String] the config file, for the error message
|
|
45
|
+
# @return [void]
|
|
46
|
+
# @raise [Error]
|
|
47
|
+
def validate!: (String path) -> void
|
|
48
|
+
|
|
49
|
+
# The YAML written to a new config file.
|
|
50
|
+
#
|
|
51
|
+
# @return [String]
|
|
52
|
+
def to_yaml: () -> String
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -13,6 +13,21 @@ module Yarbs
|
|
|
13
13
|
# @return [RBS::Types::t] the best matching RBS type, or +untyped+
|
|
14
14
|
def self.convert: (Array[String] | nil yard_types, ?strict: bool) -> RBS::Types::t
|
|
15
15
|
|
|
16
|
+
# Number of type parameters for the core generic classes, which YARD
|
|
17
|
+
# lets you write with any number of arguments (or none) but RBS doesn't.
|
|
18
|
+
GENERIC_ARITY: { Array: 1, Set: 1, Range: 1, Enumerable: 1, Enumerator: 2, Hash: 2 }
|
|
19
|
+
|
|
20
|
+
# Fits the arguments of core generics to what RBS expects, recursively:
|
|
21
|
+
#
|
|
22
|
+
# - bare `Array`/`Hash` -> `Array[untyped]`/`Hash[untyped, untyped]`
|
|
23
|
+
# - `Array<String, Regexp>` (YARD for "Strings or Regexps") ->
|
|
24
|
+
# `Array[String | Regexp]`
|
|
25
|
+
# - missing trailing arguments are filled with `untyped`
|
|
26
|
+
#
|
|
27
|
+
# @param type [RBS::Types::t]
|
|
28
|
+
# @return [RBS::Types::t]
|
|
29
|
+
def self.fit_generic_args: (RBS::Types::t type) -> RBS::Types::t
|
|
30
|
+
|
|
16
31
|
# `Proc<(ArgType, ...), ReturnType>` -> `^(ArgType, ...) -> ReturnType`,
|
|
17
32
|
# RBS's proc-literal type (a real, typed callable signature).
|
|
18
33
|
#
|
data/sig/yarbs/version.rbs
CHANGED
data/sig/yarbs/watcher.rbs
CHANGED
|
@@ -14,6 +14,15 @@ module Yarbs
|
|
|
14
14
|
# @param output_dir [String] directory to write generated .rbs files into
|
|
15
15
|
def initialize: (Array[String] patterns, output_dir: String) -> void
|
|
16
16
|
|
|
17
|
+
# The directory a glob pattern searches under: everything before its
|
|
18
|
+
# first glob character, e.g. "lib/**/*.rb" -> "lib". Used both as the
|
|
19
|
+
# directory to hand to `Listen.to` and by `yarbs init` for the
|
|
20
|
+
# Steepfile's `check`.
|
|
21
|
+
#
|
|
22
|
+
# @param pattern [String]
|
|
23
|
+
# @return [String]
|
|
24
|
+
def self.base_directory: (String pattern) -> String
|
|
25
|
+
|
|
17
26
|
# Starts watching and blocks until interrupted (Ctrl-C).
|
|
18
27
|
#
|
|
19
28
|
# @return [void]
|
|
@@ -32,9 +41,5 @@ module Yarbs
|
|
|
32
41
|
def relative_path: (untyped path) -> untyped
|
|
33
42
|
|
|
34
43
|
def watch_directories: () -> untyped
|
|
35
|
-
|
|
36
|
-
# The directory to hand to `Listen.to` for a glob pattern: everything
|
|
37
|
-
# before its first glob character, e.g. "lib/**/*.rb" -> "lib".
|
|
38
|
-
def base_directory: (untyped pattern) -> (untyped | ".")
|
|
39
44
|
end
|
|
40
45
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yarbs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- tobidelius
|
|
@@ -65,6 +65,8 @@ files:
|
|
|
65
65
|
- lib/yarbs.rb
|
|
66
66
|
- lib/yarbs/annotator.rb
|
|
67
67
|
- lib/yarbs/cli.rb
|
|
68
|
+
- lib/yarbs/cli/init.rb
|
|
69
|
+
- lib/yarbs/config.rb
|
|
68
70
|
- lib/yarbs/generator.rb
|
|
69
71
|
- lib/yarbs/prototype_builder.rb
|
|
70
72
|
- lib/yarbs/type_converter.rb
|
|
@@ -74,6 +76,8 @@ files:
|
|
|
74
76
|
- sig/yarbs.rbs
|
|
75
77
|
- sig/yarbs/annotator.rbs
|
|
76
78
|
- sig/yarbs/cli.rbs
|
|
79
|
+
- sig/yarbs/cli/init.rbs
|
|
80
|
+
- sig/yarbs/config.rbs
|
|
77
81
|
- sig/yarbs/generator.rbs
|
|
78
82
|
- sig/yarbs/prototype_builder.rbs
|
|
79
83
|
- sig/yarbs/type_converter.rbs
|