yard-rustdoc 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 79bdb3aec8e1f714e0171aa730ac8f7ae76a2c03a833469e881c509965b2e570
4
+ data.tar.gz: 3b9c82a3687ac3dd58f94932e63f1a71ef390314f7a1940dd2b1f2618aec8845
5
+ SHA512:
6
+ metadata.gz: eff3df27844e46ad1ff348af7149e7e87758249f0478afbd88b68692ba84338a9b1a970091b14f703491b6cf78738ebb61b16dc3eae2bc8c49c450ea9f1a2d7d
7
+ data.tar.gz: be52e56f4bb91b3047b57b8fd1518b9c094e74b0601fdc32349c3e425beac0a6914589997955a9fc37f2a0673c35336cf77dd77b269284578d53092d4f7d26a2
data/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # YARD::Rustdoc
2
+
3
+ YARD plugin for documenting Magnus-based Rust gems. Supports writing class
4
+ documentation on Struct and method documentation on Struct methods.
5
+
6
+ **Note**: WIP, not released.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'yard-rustdoc'
14
+ ```
15
+
16
+ Load the plugin through `--plugin rustdoc` (e.g. in your project's `.yardopts`).
17
+ See `test/samples/example-ext` for full example.
18
+
19
+ ## Usage
20
+
21
+ 1. Write YARD-compatible documentation in Rust documentation block
22
+ (`///` or `//!`), and tag them with `@yard`.
23
+
24
+ 2. Generate Rustdoc as JSON:
25
+
26
+ ```sh
27
+ cargo +nightly rustdoc -p path/to/extension -- \
28
+ -Zunstable-options --output-format json \
29
+ --document-private-items
30
+ ```
31
+
32
+ `nightly` is required because the JSON format isn't stable yet.
33
+ `--document-private-items` is included so that you don't have to make
34
+ everything in the crate public.
35
+
36
+ 3. Run YARD on Ruby files and the Rustdoc's JSON:
37
+
38
+ ```sh
39
+ yard lib path/to/rustdoc.json
40
+ ```
41
+
42
+ ### Writing documentation
43
+
44
+ YARD::Rustdoc only targets docblocks starting with `@yard` so that you can
45
+ still write Rust-style docblocks for non-Ruby parts of your crate.
46
+
47
+ #### Examples
48
+
49
+ The class name will be extracted from Magnus' `class =`.
50
+
51
+ ```rust
52
+ /// @yard
53
+ /// High-level documentation for Foo::Bar.
54
+ #[magnus(class = "Foo::Bar")]
55
+ pub struct Bar { }
56
+ ```
57
+
58
+ The `@rename` tag renames the class to `Foo::Baz`.
59
+
60
+ ```rust
61
+ /// @yard
62
+ /// @remame Foo::Baz
63
+ pub struct InnerName { }
64
+ ```
65
+
66
+ Defines `Foo::Bar.new` -- class method because the first argument isn't `self`.
67
+
68
+ ```rust
69
+ impl Bar {
70
+ /// @yard
71
+ /// @return [Foo::Bar]
72
+ fn build() -> Self {}
73
+ }
74
+ ```
75
+
76
+ Defines `Foo::Bar#baz`:
77
+
78
+ ```rust
79
+ impl Bar {
80
+ /// @yard
81
+ fn baz(&self) {}
82
+ }
83
+ ```
84
+
85
+ Specifies the method's name and params with `@def`. This lets YARD know which params
86
+ are required, optional, keyword arguments, etc.
87
+
88
+ `@def` must be a single, valid ruby method definition, without the `end`.
89
+
90
+ ```rust
91
+ impl Bar {
92
+ /// @yard
93
+ /// @def qux=(val = "")
94
+ /// @param val [Object]
95
+ fn write_qux(&self, val: Value) {}
96
+ }
97
+ ```
98
+
99
+ This will be ignored as it's not tagged with `@yard`.
100
+
101
+ ```rust
102
+ impl Bar {
103
+ fn secret {}
104
+ }
105
+ ```
106
+
107
+ #### Tips
108
+
109
+ YARD's syntax differs from what Rustdoc expects. Linters you man want to disable:
110
+
111
+ ```rust
112
+ #![allow(rustdoc::broken_intra_doc_links)]
113
+ #![allow(rustdoc::invalid_html_tags)]
114
+ ```
115
+
116
+ ## Development
117
+
118
+ Run tests with `rake`. The tests use a sample project located in
119
+ `test/samples/example-ext`. To regenerate its json doc, run `rake doc:rustdoc`
120
+ from that directory. See the test project's Rakefile for details.
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "syntax_tree"
4
+
5
+ module YARD
6
+ module Rustdoc
7
+ class DefParser
8
+ # The name of the `def`'d method
9
+ attr_reader(:name)
10
+
11
+ # The params of the `def`'d method
12
+ # @return [Array<[String, [String, nil]]>] An array of tuple where 0 is
13
+ # the name of the arg with its specifier (&, **, :) and 1 is the default.
14
+ attr_reader(:parameters)
15
+
16
+ def initialize(string)
17
+ @string = string
18
+ @name = nil
19
+ @parameters = []
20
+ parse!
21
+ end
22
+
23
+ private
24
+
25
+ def parse!
26
+ string = "def #{@string}; end"
27
+ def_node = begin
28
+ SyntaxTree.parse(string) # Program
29
+ .child_nodes.first # Statements
30
+ .child_nodes.first # Def
31
+ rescue => e
32
+ log.debug(e)
33
+ return log.warn("failed to extract def node from '#{@string}'\n #{e.message}")
34
+ end
35
+ @name = def_node.name.value
36
+
37
+ params = def_node.params
38
+ params = params.contents if params.is_a?(SyntaxTree::Paren)
39
+
40
+ params.requireds.each { |name| add_param(name) }
41
+ params.optionals.each { |name, default| add_param(name, default) }
42
+ add_param(params.rest) if params.rest
43
+ params.posts.each { |name| add_param(name) }
44
+ params.keywords.each { |name, default| add_param(name, default) }
45
+ add_param(params.keyword_rest) if params.keyword_rest
46
+
47
+ add_param(params.block) if params.block
48
+ end
49
+
50
+ def format(node)
51
+ formatter = SyntaxTree::Formatter.new(nil)
52
+ node.format(formatter)
53
+ formatter.flush
54
+ formatter.output
55
+ end
56
+
57
+ def add_param(node, default = nil)
58
+ default = format(default) if default
59
+
60
+ @parameters << [format(node), default]
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YARD::Handlers
4
+ module Rustdoc
5
+ class Base < YARD::Handlers::Base
6
+ include YARD::Parser::Rustdoc
7
+
8
+ def self.statement_class(klass = nil)
9
+ @statement_classes ||= []
10
+ @statement_classes << klass
11
+
12
+ nil
13
+ end
14
+
15
+ def self.handles?(statement, processor)
16
+ handles = true
17
+ if @statement_classes.any?
18
+ handles &&= @statement_classes.any? { |klass| statement.is_a?(klass) }
19
+ end
20
+
21
+ handles
22
+ end
23
+
24
+ def register_file_info(object, file = statement.file, line = statement.line, comments = statement.docstring)
25
+ super
26
+ end
27
+
28
+ def register_docstring(object, docstring = statement.docstring, stmt = statement)
29
+ super
30
+ end
31
+ end
32
+
33
+ class StructHandler < Base
34
+ statement_class(Statements::Struct)
35
+
36
+ process do
37
+ obj = YARD::CodeObjects::ClassObject.new(:root, statement.name)
38
+ register(obj)
39
+
40
+ push_state(namespace: obj) do
41
+ parser.process(statement.methods)
42
+ end
43
+ end
44
+ end
45
+
46
+ class MethodHandler < Base
47
+ statement_class(Statements::Method)
48
+
49
+ process do
50
+ obj = MethodObject.new(namespace, statement.name, statement.scope)
51
+ obj.parameters = statement.parameters if statement.parameters
52
+ register(obj)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YARD::Parser::Rustdoc
4
+ class Parser < YARD::Parser::Base
5
+ # This default constructor does nothing. The subclass is responsible for
6
+ # storing the source contents and filename if they are required.
7
+ # @param [String] source the source contents
8
+ # @param [String] filename the name of the file if from disk
9
+ def initialize(source, filename)
10
+ @source = source
11
+ @rustdoc_json = JSON.parse(@source).fetch("index") do
12
+ raise "Expected `index` top-level key in Rustdoc json format"
13
+ end
14
+ @filename = filename
15
+ @entries = []
16
+ end
17
+
18
+ # Override inspect instead of dumping the file content because it is huge.
19
+ def inspect
20
+ "<#{self.class.name} @filename=#{@filename.inspect}>"
21
+ end
22
+
23
+ # Finds Rust Struct for the current crate marked with @yard and extract all
24
+ # the marked methods.
25
+ # @return [Base] this method should return itself
26
+ def parse
27
+ @entries = []
28
+
29
+ @rustdoc_json.each do |id, entry|
30
+ next unless relevant_entry?(entry)
31
+ next unless entry["kind"] == "struct"
32
+
33
+ methods = entry
34
+ .dig("inner", "impls")
35
+ .flat_map { |impl_id| @rustdoc_json.dig(impl_id, "inner", "items") }
36
+ .filter_map do |method_id|
37
+ method_entry = @rustdoc_json.fetch(method_id)
38
+ next unless relevant_entry?(method_entry)
39
+
40
+ Statements::Method.new(method_entry)
41
+ end
42
+
43
+ @entries << Statements::Struct.new(entry, methods)
44
+ end
45
+
46
+ self
47
+ end
48
+
49
+ def tokenize
50
+ raise "Rustdoc Parser does not tokenize"
51
+ end
52
+
53
+ # This method should be implemented to return a list of semantic tokens
54
+ # representing the source code to be post-processed. Otherwise the method
55
+ # should return nil.
56
+ #
57
+ # @abstract
58
+ # @return [Array] a list of semantic tokens representing the source code
59
+ # to be post-processed
60
+ # @return [nil] if no post-processing should be done
61
+ def enumerator
62
+ @entries
63
+ end
64
+
65
+ private
66
+
67
+ def relevant_entry?(entry)
68
+ return false unless entry["crate_id"].zero?
69
+ return false unless entry["docs"]&.include?("@yard")
70
+
71
+ true
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YARD::Parser::Rustdoc
4
+ module Statements
5
+ class Base
6
+ def initialize(rustdoc)
7
+ @rustdoc = rustdoc
8
+ @name = nil
9
+ @parameters = []
10
+ end
11
+
12
+ def docstring
13
+ @rustdoc.fetch("docs")
14
+ end
15
+
16
+ def source
17
+ return if file.nil?
18
+
19
+ File.read(file)
20
+ .lines
21
+ .slice(line_index_range)
22
+ .join
23
+ rescue Errno::ENOENT
24
+ log.warn("can't read '#{file}', cwd='#{Dir.pwd}'")
25
+
26
+ ""
27
+ end
28
+
29
+ def line
30
+ line_range.first
31
+ end
32
+
33
+ def line_range
34
+ @rustdoc.dig("span", "begin", 0)...@rustdoc.dig("span", "end", 0)
35
+ end
36
+
37
+ def file
38
+ @rustdoc.dig("span", "filename")
39
+ end
40
+
41
+ def show
42
+ @rustdoc.to_s
43
+ end
44
+
45
+ # Not sure what should go here either
46
+ def comments_hash_flag
47
+ end
48
+
49
+ def comments_range
50
+ end
51
+
52
+ private
53
+
54
+ def line_index_range
55
+ (@rustdoc.dig("span", "begin", 0) - 1)..@rustdoc.dig("span", "end", 0)
56
+ end
57
+ end
58
+
59
+ class Struct < Base
60
+ attr_reader(:methods)
61
+
62
+ def initialize(rustdoc, methods)
63
+ super(rustdoc)
64
+ @methods = methods
65
+ end
66
+
67
+ def name
68
+ return $1.strip if docstring =~ /^@rename\s*(.+)/
69
+
70
+ @rustdoc["attrs"].each do |attr|
71
+ next unless attr.include?("magnus")
72
+
73
+ # Extract class name from magnus attrs that define classes such as:
74
+ # - #[magnus::wrap(class = "ClassName")]
75
+ # - #[magnus(class = "ClassName")]
76
+ return $1.strip if attr =~ /class\s*=\s*"([^"]+)"/
77
+ end
78
+
79
+ # Fallback to the struct's name
80
+ @rustdoc.fetch("name")
81
+ end
82
+ end
83
+
84
+ class Method < Base
85
+ def name
86
+ parse_def!
87
+
88
+ @name || @rustdoc.fetch("name")
89
+ end
90
+
91
+ def scope
92
+ first_arg = @rustdoc.dig("inner", "decl", "inputs", 0, 0)
93
+ if first_arg == "self"
94
+ :instance
95
+ else
96
+ :class
97
+ end
98
+ end
99
+
100
+ # Parses the parameters from the @def annotations in the docstring
101
+ def parameters
102
+ parse_def!
103
+
104
+ @parameters
105
+ end
106
+
107
+ private
108
+
109
+ # Extract @def tag from the docstring. Has to be done before we create
110
+ # the CodeObject in the `handler` because:
111
+ # 1. The method name can be overriden -- it's too late for that once
112
+ # the method code object exists.
113
+ # 2. The docstring parser runs automatically after the code object is
114
+ # created, and emits warning on udnefined `@param`. We need to set the
115
+ # method's parameters before then.
116
+ def parse_def!
117
+ return if defined?(@def_parsed)
118
+ @def_parsed = true
119
+
120
+ parsed = YARD::DocstringParser.new.parse(@rustdoc.fetch("docs"))
121
+ def_tag = parsed.tags.find do |tag|
122
+ tag.respond_to?(:tag_name) && tag.tag_name == "def"
123
+ end
124
+
125
+ return unless def_tag
126
+
127
+ parser = YARD::Rustdoc::DefParser.new(def_tag.text)
128
+ @name = parser.name
129
+ @parameters = parser.parameters || []
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YARD
4
+ module Rustdoc
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "yard"
5
+
6
+ require_relative "yard-rustdoc/version"
7
+ require_relative "yard-rustdoc/parser"
8
+ require_relative "yard-rustdoc/statements"
9
+ require_relative "yard-rustdoc/handlers"
10
+ require_relative "yard-rustdoc/def_parser"
11
+
12
+ module YARD
13
+ Parser::SourceParser.register_parser_type(:rustdoc, Parser::Rustdoc::Parser, "json")
14
+ Handlers::Processor.register_handler_namespace(:rustdoc, Handlers::Rustdoc)
15
+ Tags::Library.define_tag("Tagging docblock for yard", :yard)
16
+ Tags::Library.define_tag("Renaming class & methods", :rename)
17
+ Tags::Library.define_tag("Specify a method name and args", :def)
18
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yard-rustdoc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jimmy Bourassa
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-10-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: yard
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.9'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: syntax_tree
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: standard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.9'
83
+ description:
84
+ email:
85
+ - jbourassa@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - README.md
91
+ - lib/yard-rustdoc.rb
92
+ - lib/yard-rustdoc/def_parser.rb
93
+ - lib/yard-rustdoc/handlers.rb
94
+ - lib/yard-rustdoc/parser.rb
95
+ - lib/yard-rustdoc/statements.rb
96
+ - lib/yard-rustdoc/version.rb
97
+ homepage: https://github.com/oxidize-rb/yard-rustdoc
98
+ licenses:
99
+ - MIT
100
+ metadata:
101
+ homepage_uri: https://github.com/oxidize-rb/yard-rustdoc
102
+ source_code_uri: https://github.com/oxidize-rb/yard-rustdoc
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 2.7.0
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubygems_version: 3.3.7
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Generate YARD documentation for Magnus-based Rust gems.
122
+ test_files: []