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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad6ad92289f8a7d901231eadb81be04511912ffd65f183d0cd00d4f9d2b9b851
4
- data.tar.gz: 1cae1aaf74795fef87a77eafd33b32c7a8252dd3c0660032d11c9985213ab965
3
+ metadata.gz: 601e10f12020d139a62bd8655f95b6c56bb5037d012265abc5d7adddfda0d60d
4
+ data.tar.gz: 34d6a692e0d7323d3fba7407b9586c1ddccdcba755c3333673cb520d28b6d762
5
5
  SHA512:
6
- metadata.gz: dce1a1abee4e2d215abf7fc9ca707c00dc166790d86daa644d2bd43ab29348dade7eda387ada1ac39d0bad428e19f4d19321cf57af465d5490376d06fb02b78d
7
- data.tar.gz: 84096e966259189840b11c1e7d02d5e2b1cbd29fe429d262e9e8733d0d503505ae92706ac3effcbdaafe6fb903fe152bb69cad59159d979862f7cbd37726451e
6
+ metadata.gz: 947060cd7c782f1e3280a12f4ea8e01eb47460d3820c58bcbfd87c6a780907e8d28b5958d2b827bc0d9987ec9f84b9a6010da57310cc7d1878164972eba8b1ee
7
+ data.tar.gz: fa4fc3e6a0e9d4f2a78d5762aeb841426b0a7bbf23a8d4de60c75ffd6014891aa87868ee3eccb22d6dedbc42ab8e5099b9c00885959bc7580c2efcefe1967477
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Yarbs
2
2
 
3
- [![Ruby](https://github.com/tobidelius/yarbs/actions/workflows/main.yml/badge.svg)](https://github.com/tobidelius/yarbs/actions/workflows/main.yml)
3
+ [![CI](https://github.com/tobidelius/yarbs/actions/workflows/ci.yml/badge.svg)](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
- Point yarbs at your source files with one or more glob patterns; it writes
64
- one `.rbs` file per source file into `sig/` (or `-o DIR` to choose a
65
- different output directory), mirroring the source layout with a leading
66
- `lib/`/`app/` stripped:
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
- output_dir = "sig"
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 = "Usage: yarbs [options] GLOB [GLOB ...]"
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
- if patterns.empty?
33
- warn parser.help
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
@@ -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
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yarbs
4
- VERSION = "0.1.1"
4
+ # The current version of yarbs.
5
+ #
6
+ # @return [String]
7
+ VERSION = "0.2.0"
5
8
  end
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
@@ -4,6 +4,7 @@ require "yard"
4
4
  require "rbs"
5
5
 
6
6
  require_relative "yarbs/version"
7
+ require_relative "yarbs/config"
7
8
  require_relative "yarbs/type_converter"
8
9
  require_relative "yarbs/prototype_builder"
9
10
  require_relative "yarbs/annotator"
@@ -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
  #
@@ -1,3 +1,6 @@
1
1
  module Yarbs
2
- VERSION: "0.1.0"
2
+ # The current version of yarbs.
3
+ #
4
+ # @return [String]
5
+ VERSION: String
3
6
  end
@@ -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.1.1
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