typedocs 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.simplecov +11 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +232 -0
- data/Rakefile +2 -0
- data/bin/typedocs +64 -0
- data/lib/typedocs.rb +78 -0
- data/lib/typedocs/arguments_spec.rb +126 -0
- data/lib/typedocs/block_spec.rb +51 -0
- data/lib/typedocs/context.rb +38 -0
- data/lib/typedocs/dsl.rb +37 -0
- data/lib/typedocs/enable.rb +1 -0
- data/lib/typedocs/fallback.rb +6 -0
- data/lib/typedocs/fallback/impl.rb +19 -0
- data/lib/typedocs/grammer_printer.rb +45 -0
- data/lib/typedocs/method_spec.rb +88 -0
- data/lib/typedocs/multi_functional_interface.rb +10 -0
- data/lib/typedocs/parser.rb +28 -0
- data/lib/typedocs/parser/ast_builder.rb +70 -0
- data/lib/typedocs/parser/object_builder.rb +104 -0
- data/lib/typedocs/type_spec.rb +210 -0
- data/lib/typedocs/version.rb +3 -0
- data/spec/example_spec.rb +130 -0
- data/spec/fallback_spec.rb +28 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/typedef_spec.rb +76 -0
- data/spec/typedocs/parser/ast_builder_spec.rb +60 -0
- data/spec/typedocs/parser/object_builder_spec.rb +43 -0
- data/spec/typedocs_spec.rb +598 -0
- data/typedocs.gemspec +22 -0
- metadata +165 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
# Ruby argument pattern:
|
2
|
+
# - required* optional* (rest requied*)?
|
3
|
+
# - optional+ requied* # optional is matched forward-wise
|
4
|
+
#
|
5
|
+
# s1 +-opt-> s2 +--req--> s3
|
6
|
+
# | |
|
7
|
+
# +----------+---------+--rest--> s6 -req-> s7
|
8
|
+
# | / /
|
9
|
+
# `-req-> s4 -opt-> s5
|
10
|
+
class Typedocs::ArgumentsSpec
|
11
|
+
def initialize
|
12
|
+
# [[type, [spec ...]] ...]
|
13
|
+
@specs = []
|
14
|
+
@current = nil
|
15
|
+
end
|
16
|
+
def empty?
|
17
|
+
@specs.empty?
|
18
|
+
end
|
19
|
+
def valid?(args)
|
20
|
+
matched = match(args)
|
21
|
+
matched && matched.all? {|arg, spec| spec.valid? arg}
|
22
|
+
end
|
23
|
+
def error_message_for(args)
|
24
|
+
matched = match(args)
|
25
|
+
errors = matched.select{|arg, spec|!spec.valid?(arg)}
|
26
|
+
"Expected: #{to_source}. Errors: #{errors.map{|arg,spec|spec.error_message_for(arg)}.join(' ||| ')}"
|
27
|
+
end
|
28
|
+
def to_source
|
29
|
+
@specs.flat_map{|t,s|
|
30
|
+
attr =
|
31
|
+
case t
|
32
|
+
when :req
|
33
|
+
''
|
34
|
+
when :opt
|
35
|
+
'?'
|
36
|
+
when :res
|
37
|
+
'*'
|
38
|
+
else
|
39
|
+
raise
|
40
|
+
end
|
41
|
+
s.map{|spec| "#{attr}#{spec.to_source}" }
|
42
|
+
}.join(' -> ')
|
43
|
+
end
|
44
|
+
def add_required(arg_spec)
|
45
|
+
_add :req, arg_spec
|
46
|
+
end
|
47
|
+
def add_optional(arg_spec)
|
48
|
+
_add :opt, arg_spec
|
49
|
+
end
|
50
|
+
def add_rest(arg_spec)
|
51
|
+
_add :res, arg_spec
|
52
|
+
end
|
53
|
+
private
|
54
|
+
# args:[...] -> success:[[arg,spec]...] | fail:nil
|
55
|
+
def match(args)
|
56
|
+
args = args.dup
|
57
|
+
types = @specs.map{|t,s|t}
|
58
|
+
case types
|
59
|
+
when [:opt, :req]
|
60
|
+
opt, req = @specs.map{|t,s|s}
|
61
|
+
return nil unless (req.length..(req.length+opt.length)) === args.size
|
62
|
+
args[0...-req.length].zip(opt).to_a + req.zip(args[-req.length..-1]).to_a
|
63
|
+
else
|
64
|
+
# [reqs, opts, rest, reqs]
|
65
|
+
partial = []
|
66
|
+
i = 0
|
67
|
+
if types[i] == :req
|
68
|
+
partial.push @specs[i][1]
|
69
|
+
i += 1
|
70
|
+
else
|
71
|
+
partial.push []
|
72
|
+
end
|
73
|
+
if types[i] == :opt
|
74
|
+
partial.push @specs[i][1]
|
75
|
+
i += 1
|
76
|
+
else
|
77
|
+
partial.push []
|
78
|
+
end
|
79
|
+
if types[i] == :res
|
80
|
+
partial.push @specs[i][1]
|
81
|
+
i += 1
|
82
|
+
else
|
83
|
+
partial.push []
|
84
|
+
end
|
85
|
+
if types[i] == :req
|
86
|
+
partial.push @specs[i][1]
|
87
|
+
i += 1
|
88
|
+
else
|
89
|
+
partial.push []
|
90
|
+
end
|
91
|
+
return nil unless i == types.length
|
92
|
+
reqs, opts, rest, reqs2 = partial
|
93
|
+
raise unless rest.length < 2
|
94
|
+
|
95
|
+
len_min = reqs.length + reqs2.length
|
96
|
+
if rest.empty?
|
97
|
+
len_max = reqs.length + opts.length + reqs2.length
|
98
|
+
return nil unless (len_min..len_max) === args.length
|
99
|
+
else
|
100
|
+
return nil unless len_min <= args.length
|
101
|
+
end
|
102
|
+
reqs_args = args.shift(reqs.length)
|
103
|
+
reqs2_args = args.pop(reqs2.length)
|
104
|
+
opts_args = args.shift([opts.length, args.length].min)
|
105
|
+
rest_args = args
|
106
|
+
|
107
|
+
rest_spec = rest[0]
|
108
|
+
return [
|
109
|
+
*reqs_args.zip(reqs),
|
110
|
+
*opts_args.zip(opts),
|
111
|
+
*(rest_spec ? rest_args.map{|a|[a, rest_spec]} : []),
|
112
|
+
*reqs2_args.zip(reqs2),
|
113
|
+
]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
def _add(type,spec)
|
117
|
+
Typedocs.ensure_klass(spec, Typedocs::TypeSpec)
|
118
|
+
if @current == type
|
119
|
+
@specs.last[1].push spec
|
120
|
+
else
|
121
|
+
@specs.push [type, [spec]]
|
122
|
+
@current = type
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Typedocs
|
2
|
+
class BlockSpec
|
3
|
+
def initialize(type, name)
|
4
|
+
raise ArgumentError unless [:req, :opt, :none].include?(type)
|
5
|
+
@name = name
|
6
|
+
@type = type
|
7
|
+
end
|
8
|
+
attr_reader :name
|
9
|
+
def valid?(block)
|
10
|
+
case block
|
11
|
+
when nil
|
12
|
+
return @type == :opt || @type == :none
|
13
|
+
when Proc
|
14
|
+
return @type == :opt || @type == :req
|
15
|
+
else
|
16
|
+
raise 'maybe typedocs bug'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def error_message_for(block)
|
20
|
+
raise ArgumentError if valid?(block)
|
21
|
+
case @type
|
22
|
+
when :req
|
23
|
+
"Block not given"
|
24
|
+
when :none
|
25
|
+
"Block not allowed"
|
26
|
+
else
|
27
|
+
raise 'maybe typedocs bug'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
def to_source
|
31
|
+
n = name
|
32
|
+
case @type
|
33
|
+
when :req
|
34
|
+
"&#{n}"
|
35
|
+
when :opt
|
36
|
+
"?&#{n}"
|
37
|
+
when :none
|
38
|
+
''
|
39
|
+
else
|
40
|
+
raise "Invalid type: #{@type}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
def to_source_with_arrow
|
44
|
+
if @type == :none
|
45
|
+
''
|
46
|
+
else
|
47
|
+
"#{to_source} -> "
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Typedocs::Context
|
2
|
+
def self.valid_udt_name?(name)
|
3
|
+
/\A@[A-Z][a-zA-Z0-9]*\Z/ =~ name.to_s
|
4
|
+
end
|
5
|
+
|
6
|
+
def initialize(klass)
|
7
|
+
@klass = klass
|
8
|
+
# udt_name => spec
|
9
|
+
@specs = {}
|
10
|
+
end
|
11
|
+
def typedef(name, definition)
|
12
|
+
raise ArgumentError, "Invalid user-defined type name: #{name}" unless self.class.valid_udt_name?(name)
|
13
|
+
@specs[name.to_s] = Typedocs::Parser.new.parse(@klass, definition, :type)
|
14
|
+
end
|
15
|
+
def defined_type!(name)
|
16
|
+
self_defined_type(name) || outer_defined_type(name) || (raise Typedocs::NoSuchType, "Type not found in #{@klass.name}: #{name}")
|
17
|
+
end
|
18
|
+
def defined_type(name)
|
19
|
+
self_defined_type(name) || outer_defined_type(name) || parent_defined_type(name)
|
20
|
+
end
|
21
|
+
def self_defined_type(name)
|
22
|
+
@specs[name]
|
23
|
+
end
|
24
|
+
def outer_defined_type(name)
|
25
|
+
return nil unless @klass.name
|
26
|
+
outer_name = @klass.name.split(/::/)[0..-2]
|
27
|
+
unless outer_name.empty?
|
28
|
+
outer_klass = outer_name.inject(::Object) {|ns, name| ns.const_get(name) }
|
29
|
+
Typedocs.context(outer_klass).defined_type(name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
def parent_defined_type(name)
|
33
|
+
return nil unless @kass.kind_of? ::Class
|
34
|
+
superclass = @klass.superclass
|
35
|
+
return nil unless superclass
|
36
|
+
Typedocs.context(superclass).defined_type(name)
|
37
|
+
end
|
38
|
+
end
|
data/lib/typedocs/dsl.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Typedocs::DSL
|
2
|
+
def self.included(klass)
|
3
|
+
# doc:String | special:(:inherit) | nil
|
4
|
+
@typedocs_current_def = nil
|
5
|
+
|
6
|
+
class << klass
|
7
|
+
def tdoc(*args)
|
8
|
+
if args.size == 1 && args[0].is_a?(String) || args[0].is_a?(Symbol)
|
9
|
+
@typedocs_current_def = args[0]
|
10
|
+
elsif args.size == 0
|
11
|
+
Typedocs::MultiFunctionalInterface.new(self)
|
12
|
+
else
|
13
|
+
raise ArgumentError
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_added(name)
|
18
|
+
return unless @typedocs_current_def
|
19
|
+
|
20
|
+
method_spec = ::Typedocs.create_method_spec(self, name, @typedocs_current_def)
|
21
|
+
@typedocs_current_def = nil
|
22
|
+
|
23
|
+
::Typedocs.define_spec self, name, method_spec
|
24
|
+
end
|
25
|
+
|
26
|
+
def singleton_method_added(name)
|
27
|
+
return unless @typedocs_current_def
|
28
|
+
|
29
|
+
method_spec = ::Typedocs.create_method_spec(self, name, @typedocs_current_def)
|
30
|
+
@typedocs_current_def = nil
|
31
|
+
|
32
|
+
singleton_class = class << self; self; end
|
33
|
+
::Typedocs.define_spec singleton_class, name, method_spec
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
TYPEDOCS_ENABLED = 1
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module TypedocsFallback
|
2
|
+
def self.method_missing(name, *args)
|
3
|
+
nil
|
4
|
+
end
|
5
|
+
|
6
|
+
NULL_OBJECT = BasicObject.new
|
7
|
+
def NULL_OBJECT.method_missing(name, *args)
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
module DSL
|
12
|
+
def self.included(klass)
|
13
|
+
def klass.tdoc(*args)
|
14
|
+
NULL_OBJECT
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Typedocs::GrammerPrinter
|
2
|
+
def self.print_grammer(out)
|
3
|
+
parser = Typedocs::Parser::ASTBuilder.new
|
4
|
+
print_recursive(out, parser.root)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.print_recursive(out, root)
|
8
|
+
waiting = [root]
|
9
|
+
ignore = {}
|
10
|
+
|
11
|
+
until waiting.empty?
|
12
|
+
entity = waiting.pop
|
13
|
+
next if ignore[entity]
|
14
|
+
ignore[entity] = true
|
15
|
+
|
16
|
+
if entity.kind_of?(Parslet::Atoms::Entity)
|
17
|
+
out.puts "#{'%20s' % entity.to_s} <- #{entity.parslet.to_s.gsub(/[a-z_]+:/, '')}"
|
18
|
+
end
|
19
|
+
|
20
|
+
atoms = Parslet::Atoms
|
21
|
+
|
22
|
+
case entity
|
23
|
+
when atoms::Sequence
|
24
|
+
entity.parslets.reverse.each do|pl|
|
25
|
+
waiting.push pl
|
26
|
+
end
|
27
|
+
when atoms::Lookahead
|
28
|
+
waiting.push entity.bound_parslet
|
29
|
+
when atoms::Repetition
|
30
|
+
waiting.push entity.parslet
|
31
|
+
when atoms::Alternative
|
32
|
+
entity.alternatives.reverse.each do|pl|
|
33
|
+
waiting.push pl
|
34
|
+
end
|
35
|
+
when atoms::Entity
|
36
|
+
waiting.push entity.parslet
|
37
|
+
when atoms::Named
|
38
|
+
waiting.push entity.parslet
|
39
|
+
when atoms::Re, atoms::Str
|
40
|
+
else
|
41
|
+
raise "Unsupported class: #{entity.class}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Typedocs
|
2
|
+
module MethodSpec
|
3
|
+
class AnyOf
|
4
|
+
# [MethodSpec::Single] ->
|
5
|
+
def initialize(specs)
|
6
|
+
specs.each do|spec|
|
7
|
+
Typedocs.ensure_klass(spec, Typedocs::MethodSpec::Single)
|
8
|
+
end
|
9
|
+
@specs = specs
|
10
|
+
end
|
11
|
+
|
12
|
+
def call_with_validate(method, *args, &block)
|
13
|
+
spec = nil
|
14
|
+
@specs.each do|s|
|
15
|
+
begin
|
16
|
+
s.validate_caller(args, block)
|
17
|
+
spec = s
|
18
|
+
break
|
19
|
+
rescue Typedocs::ArgumentError, Typedocs::BlockError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
unless spec
|
24
|
+
raise Typedocs::ArgumentError, "Arguments not match any rule"
|
25
|
+
end
|
26
|
+
|
27
|
+
ret = method.call(*args, &block)
|
28
|
+
spec.validate_retval ret
|
29
|
+
ret
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_source
|
33
|
+
@specs.map(&:to_source).join(' || ')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Single
|
38
|
+
def initialize(args_spec, block_spec, retval_spec)
|
39
|
+
Typedocs.ensure_klass(args_spec, Typedocs::ArgumentsSpec)
|
40
|
+
Typedocs.ensure_klass(block_spec, Typedocs::BlockSpec)
|
41
|
+
Typedocs.ensure_klass(retval_spec, Typedocs::TypeSpec)
|
42
|
+
@arguments_spec = args_spec
|
43
|
+
@block_spec = block_spec
|
44
|
+
@retval_spec = retval_spec
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :arguments_spec
|
48
|
+
attr_reader :block_spec
|
49
|
+
attr_reader :retval_spec
|
50
|
+
|
51
|
+
def call_with_validate(method, *args, &block)
|
52
|
+
validate_args args
|
53
|
+
validate_block block
|
54
|
+
ret = method.call *args, &block
|
55
|
+
validate_retval ret
|
56
|
+
ret
|
57
|
+
end
|
58
|
+
|
59
|
+
def validate_caller(args, block)
|
60
|
+
validate_args args
|
61
|
+
validate_block block
|
62
|
+
end
|
63
|
+
|
64
|
+
def validate_args(args)
|
65
|
+
raise Typedocs::ArgumentError, arguments_spec.error_message_for(args) unless arguments_spec.valid?(args)
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate_block(block)
|
69
|
+
raise Typedocs::BlockError, "Cant accept block" if !block_spec && block
|
70
|
+
if block_spec
|
71
|
+
raise Typedocs::BlockError, block_spec.error_message_for(block) unless block_spec.valid?(block)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def validate_retval(ret)
|
76
|
+
raise Typedocs::RetValError, retval_spec.error_message_for(ret) unless retval_spec.valid?(ret)
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_source
|
80
|
+
s = ''
|
81
|
+
s << arguments_spec.to_source
|
82
|
+
s << " -> " unless arguments_spec.empty?
|
83
|
+
s << block_spec.to_source_with_arrow
|
84
|
+
s << "#{retval_spec.to_source}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Typedocs::Parser; end
|
2
|
+
|
3
|
+
require 'typedocs/parser/ast_builder'
|
4
|
+
require 'typedocs/parser/object_builder'
|
5
|
+
|
6
|
+
class Typedocs::Parser
|
7
|
+
def parse(klass, src, type = :root)
|
8
|
+
ast = Typedocs::Parser::ASTBuilder.new
|
9
|
+
obj = Typedocs::Parser::ObjectBuilder.create_builder_for(klass)
|
10
|
+
|
11
|
+
root = ast.public_send(type)
|
12
|
+
|
13
|
+
result =
|
14
|
+
begin
|
15
|
+
obj.apply root.parse(src)
|
16
|
+
rescue Parslet::ParseFailed => e
|
17
|
+
raise e.cause.ascii_tree
|
18
|
+
rescue ArgumentError => e
|
19
|
+
error = StandardError.new("Parse error: Maybe parser's bug. Input=#{src.inspect}, Error = #{e}")
|
20
|
+
error.set_backtrace e.backtrace
|
21
|
+
raise error
|
22
|
+
end
|
23
|
+
if result.is_a?(Hash)
|
24
|
+
raise "Parse error: Maybe parser's bug or unexpected argument(type: #{type.inspect}). Input=#{src.inspect}, Result=#{result.inspect}"
|
25
|
+
end
|
26
|
+
result
|
27
|
+
end
|
28
|
+
end
|