typedocs 0.0.1
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.
- 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
|