yard-contracts 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +133 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/yard-contracts/contract_handler.rb +84 -0
- data/lib/yard-contracts/formatters.rb +260 -0
- data/lib/yard-contracts/version.rb +3 -0
- data/lib/yard-contracts.rb +5 -0
- data/yard-contracts.gemspec +35 -0
- metadata +160 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5a252e7a360f2df858df3b4e3d43c2d163f73abc
|
4
|
+
data.tar.gz: a77c9eb8b9dce8d9c4e797074f79458892665074
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 33c4cbc331e304b7b59590169d4a6f99f09ae8ed3fd3754567676942a653d8cedcc1b769f11fe9cefb5939462749d118a9c3c7fc39f855a69bfe78b3ddfb140b
|
7
|
+
data.tar.gz: 612f609be55f7ef6dfc8f39a47de8ee710e13ba76dcd042e7f3a4b97db5b08d1d9f9aeef664ba9e90f03d59d705da94aadde2261981a0d216e3122df2508bd80
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Simon George
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# yard-contracts
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/sfcgeorge/yard-contracts.svg?branch=master)](https://travis-ci.org/sfcgeorge/yard-contracts)
|
4
|
+
|
5
|
+
yard-contracts is a YARD plugin that works with the fantastic Contracts gem to automatically document types and descriptions of parameters in your method signatures, saving time, making your code concise and keeping your documentation consistent.
|
6
|
+
|
7
|
+
Have you ever got fed up of coding validations, writing error messages and then documenting those things? All this duplication and boilerplate code has always bugged me. Contracts solves the validations and error messages part already, turning many lines of repetitive code into 1 terse readable one. This extension now solves the documentation part making documentation automatically say the same as your validations.
|
8
|
+
|
9
|
+
* Types gleaned from Contract and automatically linked with the method signature then added to param documentation.
|
10
|
+
* to_s is called on the types and where it is useful, added to the param documentation as the description.
|
11
|
+
* If any params are already documented manually, the extra information from the Contract is merged in rather than overriding or duplicating; allowing full flexibility.
|
12
|
+
|
13
|
+
I've used this plugin gem on an existing project and documented 69 methods; it seems to be working great! Note I haven't used all corners of YARD, so there may be advanced features or scenarios that this doesn't work for, please open an issue if you find one. For straightforward projects, however, it's fantastic.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'yard-contracts'
|
21
|
+
```
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
$ gem install yard-contracts
|
30
|
+
|
31
|
+
## Examples
|
32
|
+
|
33
|
+
See these two equivalent(ish) examples and be blown away.
|
34
|
+
|
35
|
+
Before Contracts and yard-contracts:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
# Find the root of a number, like `Math.sqrt` for arbitrary roots.
|
39
|
+
# @param root [Numeric] Root must be greater than zero.
|
40
|
+
# @param num [Numeric] Can't take root of a negative number.
|
41
|
+
# @return [Numeric] Result will be greater than zero.
|
42
|
+
def root(root, num)
|
43
|
+
unless root.is_a?(Numeric) && root > 0
|
44
|
+
raise "root must be Numeric and > 0."
|
45
|
+
end
|
46
|
+
unless num.is_a?(Numeric) && num >= 0
|
47
|
+
raise "num must be Numeric and >= 0."
|
48
|
+
end
|
49
|
+
|
50
|
+
result = num**(1.0/root)
|
51
|
+
|
52
|
+
unless result.is_a?(Numeric) && result >= 0
|
53
|
+
raise "return wasn't Numeric or >= 0."
|
54
|
+
end
|
55
|
+
|
56
|
+
return result
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
After:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# Find the root of a number, like `Math.sqrt` for arbitrary roots.
|
64
|
+
# @param num Can't take root of a negative number.
|
65
|
+
Contract Pos, Nat => Nat
|
66
|
+
def root(root, num)
|
67
|
+
num**(1.0/root)
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
Isn't that nicer? In the above after example, `@param root ...` and `@return ...` are automatically added to the documentation with their type and no description was deemed necessary. The `num` param has a manual documentation description, that automatically has it's type merged in. This gives you the flexibility to document parameters as explicitly as you like with the minimum of duplication.
|
72
|
+
|
73
|
+
There is another option for adding more detailed parameter descriptions without duplication of the parameter name or the problem of keeping documentation in sync across methods... and it's already built into Contracts: custom type classes with `to_s`! The above example would be a bad place to do this as there's only one method, but imagine your project had lots of methods that calculate roots on numbers or pass those numbers around---you'd end up duplicating the documentation for the `num` parameter. Instead add a custom type class `Rootable` or more explicitly `RootableNum` if you prefer:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
class Rootable
|
77
|
+
def self.valid? val
|
78
|
+
Nat.valid? val
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.to_s
|
82
|
+
"(Nat) Can't take root of a negative number."
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Find the root of a number, like `Math.sqrt` for arbitrary roots.
|
87
|
+
Contract Pos, Rootable => Rootable
|
88
|
+
def root(root, num)
|
89
|
+
num**(1.0/root)
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
Wow, the code and docstring is now as terse as possible with no duplication at all. Documentation generated will include the type and custom description as before, and now `Rootable` can be used all throughout the project making everything concise and clear. Plus, run time errors will have the same description from `Rootable` making it easier to debug as your documentation matches your errors. Perfect.
|
94
|
+
|
95
|
+
For yard-contracts to pick up these custom type classes they you need to supply the path to the file they are defined in to YARD with the -e flag, and they must be defined directly under the Contracts namespace or the global namespace.
|
96
|
+
|
97
|
+
See Contracts for more information on creating custom type classes.
|
98
|
+
|
99
|
+
## Usage
|
100
|
+
|
101
|
+
YARD needs to be made aware of this plugin. Simply give it to YARD with the --plugin flag:
|
102
|
+
|
103
|
+
```
|
104
|
+
bundle exec yardoc --plugin contracts
|
105
|
+
```
|
106
|
+
|
107
|
+
If you have defined custom type classes, they need to be given to YARD as well. First they must be specified in the global namespace or directly under the Contracts namespace. Give YARD the path to your custom contracts with the -e flag:
|
108
|
+
|
109
|
+
```
|
110
|
+
bundle exec yardoc --plugin contracts -e lib/my_project/custom_contracts.rb
|
111
|
+
```
|
112
|
+
|
113
|
+
If using a local fork of yard-contracts you can specify your path to `yard_extensions.rb` with the -e flag instead.
|
114
|
+
|
115
|
+
```
|
116
|
+
bundle exec yardoc -e path/to/lib/yard_extensions.rb
|
117
|
+
```
|
118
|
+
|
119
|
+
## Development
|
120
|
+
|
121
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
122
|
+
|
123
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
124
|
+
|
125
|
+
## Contributing
|
126
|
+
|
127
|
+
Open issues, and if you can please send pull requests:
|
128
|
+
|
129
|
+
1. Fork it ( https://github.com/sfcgeorge/yard-contracts/fork )
|
130
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
131
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
132
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
133
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "yard-contracts"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# FIXME: YARD is broken for named arguments in Ruby 2.2, the problem is with
|
2
|
+
# a Ripper regression in the standard library! Very annoying.
|
3
|
+
# To use YARD you must downgrade to 2.1 temporarily until 2.2 is patched.
|
4
|
+
# https://github.com/lsegal/yard/issues/825
|
5
|
+
require 'yard'
|
6
|
+
|
7
|
+
#require 'contracts/formatters'
|
8
|
+
require 'contracts/builtin_contracts'
|
9
|
+
require 'yard-contracts/formatters'
|
10
|
+
|
11
|
+
# Run the plugin handler by supplying it to yard with the --plugin flag, e.g.
|
12
|
+
#
|
13
|
+
# bundle exec yardoc --plugin contracts
|
14
|
+
class ContractHandler < YARD::Handlers::Ruby::Base
|
15
|
+
handles method_call(:Contract)
|
16
|
+
namespace_only #only match method calls inside a namespace not inside a method
|
17
|
+
|
18
|
+
def process
|
19
|
+
# statement is a YARD attribute, subclassing YARD::Parser::Ruby::AstNode
|
20
|
+
# Here it's class will be YARD::Parser::Ruby::MethodCallNode
|
21
|
+
# MethodCallNode#line_range returns the lines the method call was over
|
22
|
+
# AstNode#line gives the first line of the node
|
23
|
+
# AstNode#traverse takes a block and yields child nodes
|
24
|
+
# AstNode#jump returns the first node matching type, otherwise returns self
|
25
|
+
|
26
|
+
# Go up the tree to namespace level, then jump to next def statement
|
27
|
+
# Note: this won't document dynamicly defined methods.
|
28
|
+
parent = statement.parent
|
29
|
+
contract_last_line = statement.line_range.last
|
30
|
+
#YARD::Parser::Ruby::MethodDefinitionNode
|
31
|
+
def_method_ast = parent.traverse do |node|
|
32
|
+
# Find the first def statement that comes after the contract we're on
|
33
|
+
break node if node.line > contract_last_line && node.def?
|
34
|
+
end
|
35
|
+
|
36
|
+
## Hacky way to test for class methods
|
37
|
+
## TODO: What about module methods? Probably broken.
|
38
|
+
scope = def_method_ast.source.match(/ self\./) ? :class : :instance
|
39
|
+
name = def_method_ast.method_name true
|
40
|
+
params = def_method_ast.parameters #YARD::Parser::Ruby::ParameterNode
|
41
|
+
contracts = statement.parameters #YARD::Parser::Ruby::AstNode
|
42
|
+
|
43
|
+
ret = Contracts::Formatters::ParamContracts.new(params, contracts).return
|
44
|
+
params = Contracts::Formatters::ParamContracts.new(params, contracts).params
|
45
|
+
docstring = YARD::DocstringParser.new.parse(statement.docstring).to_docstring
|
46
|
+
|
47
|
+
# Merge params into provided docstring otherwise there can be duplicates
|
48
|
+
docstring.tags(:param).each do |tag|
|
49
|
+
param = params.find{ |t| t[0].to_s == tag.name.to_s }
|
50
|
+
if param
|
51
|
+
params.delete(param)
|
52
|
+
tag.types ||= []
|
53
|
+
tag.types << param[1].inspect
|
54
|
+
tag.text = "#{param[2].empty? ? '' : "#{param[2]}. "}#{tag.text}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
# If the docstring didn't contain all of the params already add the rest
|
58
|
+
params.each do |param|
|
59
|
+
docstring.add_tag(
|
60
|
+
YARD::Tags::Tag.new(:param, param[2].to_s, param[1].inspect, param[0])
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Merge return into provided docstring otherwise there can be a duplicate
|
65
|
+
# NOTE: Think about what to do with multiple returns
|
66
|
+
if (tag = docstring.tag(:return))
|
67
|
+
tag.types ||= []
|
68
|
+
tag.types << ret[0].inspect
|
69
|
+
tag.text = "#{ret[1].empty? ? '' : "#{ret[1]}. "}#{tag.text}"
|
70
|
+
else
|
71
|
+
# If the docstring didn't contain a return already add it
|
72
|
+
docstring.add_tag(
|
73
|
+
YARD::Tags::Tag.new(:return, ret[1].to_s, ret[0].inspect)
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
# YARD hasn't got to the def method yet, so we create a stub of it with
|
78
|
+
# our docstring, when YARD gets to it properly it will fill in the rest.
|
79
|
+
YARD::CodeObjects::MethodObject.new(namespace, name, scope) do |o|
|
80
|
+
o.docstring = docstring
|
81
|
+
end
|
82
|
+
# No `register()` it breaks stuff! Above implicit return value is enough.
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,260 @@
|
|
1
|
+
require 'contracts/builtin_contracts'
|
2
|
+
|
3
|
+
module Contracts
|
4
|
+
# A namespace for classes related to formatting.
|
5
|
+
module Formatters
|
6
|
+
# Used to format contracts for the `Expected:` field of error output.
|
7
|
+
class Expected
|
8
|
+
def initialize(contract, full=true)
|
9
|
+
@contract, @full = contract, full
|
10
|
+
end
|
11
|
+
|
12
|
+
# Formats any type of Contract.
|
13
|
+
def contract(contract = @contract)
|
14
|
+
if contract.is_a?(Hash)
|
15
|
+
hash_contract(contract)
|
16
|
+
elsif contract.is_a?(Array)
|
17
|
+
array_contract(contract)
|
18
|
+
else
|
19
|
+
InspectWrapper.new(contract, @full)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Formats Hash contracts.
|
24
|
+
def hash_contract(hash)
|
25
|
+
@full = true
|
26
|
+
hash.inject({}) { |repr, (k, v)|
|
27
|
+
repr.merge(k => InspectWrapper.new(contract(v), @full))
|
28
|
+
}.inspect
|
29
|
+
end
|
30
|
+
|
31
|
+
# Formats Array contracts.
|
32
|
+
def array_contract(array)
|
33
|
+
@full = true
|
34
|
+
array.map{ |v| InspectWrapper.new(contract(v), @full) }.inspect
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# A wrapper class to produce correct inspect behaviour for different
|
39
|
+
# contract values - constants, Class contracts, instance contracts etc.
|
40
|
+
class InspectWrapper
|
41
|
+
def initialize(value, full=true)
|
42
|
+
@value, @full = value, full
|
43
|
+
end
|
44
|
+
|
45
|
+
# Inspect different types of contract values.
|
46
|
+
# Contracts module prefix will be removed from classes.
|
47
|
+
# Custom to_s messages will be wrapped in round brackets to differentiate
|
48
|
+
# from standard Strings.
|
49
|
+
# Primitive values e.g. 42, true, nil will be left alone.
|
50
|
+
def inspect
|
51
|
+
return '' unless full?
|
52
|
+
return @value.inspect if empty_val?
|
53
|
+
return @value.to_s if plain?
|
54
|
+
return delim(@value.to_s) if has_useful_to_s?
|
55
|
+
@value.inspect.gsub(/^Contracts::/, '')
|
56
|
+
end
|
57
|
+
|
58
|
+
def delim(value)
|
59
|
+
@full ? "(#{value})" : "#{value}"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Eliminates eronious quotes in output that plain inspect includes.
|
63
|
+
def to_s
|
64
|
+
inspect
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def empty_val?
|
69
|
+
@value.nil? || @value == ""
|
70
|
+
end
|
71
|
+
|
72
|
+
def full?
|
73
|
+
@full ||
|
74
|
+
@value.is_a?(Hash) || @value.is_a?(Array) ||
|
75
|
+
(!plain? && has_useful_to_s?)
|
76
|
+
end
|
77
|
+
|
78
|
+
def plain?
|
79
|
+
# Not a type of contract that can have a custom to_s defined
|
80
|
+
!@value.is_a?(CallableClass) && @value.class != Class
|
81
|
+
end
|
82
|
+
|
83
|
+
def has_useful_to_s?
|
84
|
+
# Useless to_s value or no custom to_s behavious defined
|
85
|
+
# Ruby < 2.0 makes inspect call to_s so this won't work
|
86
|
+
@value.to_s != "" && @value.to_s != @value.inspect
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class TypesAST
|
91
|
+
def initialize(types)
|
92
|
+
@types = types[0..-2]
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_a
|
96
|
+
types = []
|
97
|
+
@types.each_with_index do |type, i|
|
98
|
+
if i == @types.length-1
|
99
|
+
# Get the param out of the `param => result` part
|
100
|
+
types << [type.first.first.source, type.first.first]
|
101
|
+
else
|
102
|
+
types << [type.source, type]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
types
|
106
|
+
end
|
107
|
+
|
108
|
+
def result
|
109
|
+
# Get the result out of the `param => result` part
|
110
|
+
[@types.last.last.last.source, @types.last.last.last]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class ParamsAST
|
115
|
+
def initialize(params)
|
116
|
+
@params = params
|
117
|
+
end
|
118
|
+
|
119
|
+
def to_a
|
120
|
+
params = []
|
121
|
+
@params.each_with_index do |param, i|
|
122
|
+
#YARD::Parser::Ruby::AstNode
|
123
|
+
next if param.nil?
|
124
|
+
if param.type == :list
|
125
|
+
param.each do |p|
|
126
|
+
next if p.nil?
|
127
|
+
params << build_param_element(p)
|
128
|
+
end
|
129
|
+
else
|
130
|
+
params << build_param_element(param)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
params
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
def build_param_element(param)
|
138
|
+
type = param.type
|
139
|
+
ident = param.jump(:ident, :label).last.to_sym
|
140
|
+
[type, ident]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class TypeAST
|
145
|
+
def initialize(type)
|
146
|
+
@type = type
|
147
|
+
end
|
148
|
+
|
149
|
+
# Formats any type of type.
|
150
|
+
def type(type = @type)
|
151
|
+
if type.type == :hash
|
152
|
+
hash_type(type)
|
153
|
+
elsif type.type == :array
|
154
|
+
array_type(type)
|
155
|
+
else
|
156
|
+
type.source
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Formats Hash type.
|
161
|
+
def hash_type(hash)
|
162
|
+
# Ast inherits from Array not Hash so we have to enumerate :assoc nodes
|
163
|
+
# which are key value pairs of the Hash and build from that.
|
164
|
+
result = {}
|
165
|
+
hash.each do |h|
|
166
|
+
result[h[0].jump(:label).last.to_sym] = Contracts::Formatters::InspectWrapper.new(type(h[1]))
|
167
|
+
end
|
168
|
+
result
|
169
|
+
end
|
170
|
+
|
171
|
+
# Formats Array type.
|
172
|
+
def array_type(array)
|
173
|
+
# This works because Ast inherits from Array.
|
174
|
+
array.map{ |v| Contracts::Formatters::InspectWrapper.new(type(v)) }.inspect
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
class ParamContracts
|
179
|
+
def initialize(param_string, types_string)
|
180
|
+
@params = ParamsAST.new(param_string).to_a
|
181
|
+
types = TypesAST.new(types_string)
|
182
|
+
@types = types.to_a
|
183
|
+
@result = types.result
|
184
|
+
end
|
185
|
+
|
186
|
+
def params
|
187
|
+
s = []
|
188
|
+
i = named_count = 0
|
189
|
+
@params.each do |param|
|
190
|
+
param_type, param = param
|
191
|
+
|
192
|
+
on_named = param_type == :named_arg ||
|
193
|
+
(named_count > 0 && param_type == :ident)
|
194
|
+
i -= named_count if on_named
|
195
|
+
|
196
|
+
type, type_ast = @types[i]
|
197
|
+
con = get_contract_value(type)
|
198
|
+
type = TypeAST.new(type_ast).type
|
199
|
+
|
200
|
+
# Ripper has :rest_param (splat) but nothing for doublesplat,
|
201
|
+
# it's just called :ident the same as required positional params.
|
202
|
+
# This is really annoying. So we have to figure it out.
|
203
|
+
if on_named
|
204
|
+
@named_con ||= con
|
205
|
+
@named_type ||= type
|
206
|
+
if @named_con.is_a? Hash
|
207
|
+
if param_type == :named_arg
|
208
|
+
con = @named_con.delete(param)
|
209
|
+
type = @named_type.delete(param)
|
210
|
+
else
|
211
|
+
con = @named_con
|
212
|
+
type = @named_type
|
213
|
+
end
|
214
|
+
else
|
215
|
+
@named_con = con = '?'
|
216
|
+
@named_type = type = []
|
217
|
+
end
|
218
|
+
named_count = 1
|
219
|
+
end
|
220
|
+
|
221
|
+
type = Contracts::Formatters::InspectWrapper.new(type)
|
222
|
+
desc = Contracts::Formatters::Expected.new(con, false).contract
|
223
|
+
# The pluses are to escape things like curly brackets
|
224
|
+
desc = "#{desc}".empty? ? '' : "+#{desc}+"
|
225
|
+
s << [param, type, desc]
|
226
|
+
i += 1
|
227
|
+
end
|
228
|
+
s
|
229
|
+
end
|
230
|
+
|
231
|
+
def return
|
232
|
+
type, type_ast = @result
|
233
|
+
con = get_contract_value(type)
|
234
|
+
type = Contracts::Formatters::InspectWrapper.new(
|
235
|
+
TypeAST.new(type_ast).type
|
236
|
+
)
|
237
|
+
desc = Contracts::Formatters::Expected.new(con, false).contract
|
238
|
+
desc = "#{desc}".empty? ? '' : "+#{desc}+"
|
239
|
+
[type, desc]
|
240
|
+
end
|
241
|
+
|
242
|
+
private
|
243
|
+
# The contract starts as a string, but we need to get it's real value
|
244
|
+
# so that we can call to_s on it.
|
245
|
+
def get_contract_value(type)
|
246
|
+
con = type
|
247
|
+
begin
|
248
|
+
con = Contracts.const_get(type)
|
249
|
+
rescue Exception #NameError
|
250
|
+
begin
|
251
|
+
con = eval(type)
|
252
|
+
rescue Exception
|
253
|
+
con
|
254
|
+
end
|
255
|
+
end
|
256
|
+
con
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'yard-contracts/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "yard-contracts"
|
8
|
+
spec.version = YARDContracts::VERSION
|
9
|
+
spec.authors = ["Simon George"]
|
10
|
+
spec.email = ["simon@sfcgeorge.co.uk"]
|
11
|
+
|
12
|
+
spec.summary = %q{YARD Plugin for Automatic Param Docs from Contracts}
|
13
|
+
spec.description = %q{This YARD plugin uses Contracts and method signatures,
|
14
|
+
merged with your optional docstring to automatically generate parameter
|
15
|
+
documentation with type and description. It does the same for return.
|
16
|
+
}
|
17
|
+
spec.homepage = "https://github.com/sfcgeorge/yard-contracts"
|
18
|
+
spec.license = "MIT"
|
19
|
+
|
20
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
#if spec.respond_to?(:metadata)
|
24
|
+
#spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com' to prevent pushes to rubygems.org, or delete to allow pushes to any server."
|
25
|
+
#end
|
26
|
+
|
27
|
+
spec.add_dependency "yard", "~> 0.8"
|
28
|
+
spec.add_dependency "contracts", "~> 0.7"
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
33
|
+
spec.add_development_dependency "nokogiri", "~> 1.6"
|
34
|
+
spec.add_development_dependency "kramdown", "~> 1.6"
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yard-contracts
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Simon George
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-28 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.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: contracts
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.7'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.7'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.2'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: nokogiri
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.6'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.6'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: kramdown
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.6'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.6'
|
111
|
+
description: "This YARD plugin uses Contracts and method signatures,\n merged with
|
112
|
+
your optional docstring to automatically generate parameter\n documentation with
|
113
|
+
type and description. It does the same for return.\n "
|
114
|
+
email:
|
115
|
+
- simon@sfcgeorge.co.uk
|
116
|
+
executables: []
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- ".gitignore"
|
121
|
+
- ".rspec"
|
122
|
+
- ".travis.yml"
|
123
|
+
- CODE_OF_CONDUCT.md
|
124
|
+
- Gemfile
|
125
|
+
- LICENSE.txt
|
126
|
+
- README.md
|
127
|
+
- Rakefile
|
128
|
+
- bin/console
|
129
|
+
- bin/setup
|
130
|
+
- lib/yard-contracts.rb
|
131
|
+
- lib/yard-contracts/contract_handler.rb
|
132
|
+
- lib/yard-contracts/formatters.rb
|
133
|
+
- lib/yard-contracts/version.rb
|
134
|
+
- yard-contracts.gemspec
|
135
|
+
homepage: https://github.com/sfcgeorge/yard-contracts
|
136
|
+
licenses:
|
137
|
+
- MIT
|
138
|
+
metadata: {}
|
139
|
+
post_install_message:
|
140
|
+
rdoc_options: []
|
141
|
+
require_paths:
|
142
|
+
- lib
|
143
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
requirements: []
|
154
|
+
rubyforge_project:
|
155
|
+
rubygems_version: 2.4.5
|
156
|
+
signing_key:
|
157
|
+
specification_version: 4
|
158
|
+
summary: YARD Plugin for Automatic Param Docs from Contracts
|
159
|
+
test_files: []
|
160
|
+
has_rdoc:
|