anise 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby +59 -38
- data/.yardopts +7 -0
- data/DEMO.md +242 -0
- data/{HISTORY.rdoc → HISTORY.md} +28 -7
- data/LICENSE.txt +27 -0
- data/README.md +129 -0
- data/demo/01_annotations.md +81 -0
- data/demo/03_attributes.md +14 -0
- data/demo/04_methods.md +50 -0
- data/demo/05_variables.md +45 -0
- data/{qed → demo}/applique/ae.rb +0 -0
- data/demo/applique/anise.rb +1 -0
- data/{qed/toplevel/01_annotations.qed → demo/toplevel/01_annotations.md} +5 -9
- data/demo/toplevel/03_attributes.md +20 -0
- data/lib/anise.rb +28 -45
- data/lib/anise.yml +59 -38
- data/lib/anise/annotations.rb +132 -0
- data/lib/anise/annotations/store.rb +136 -0
- data/lib/anise/annotative.rb +7 -0
- data/lib/anise/annotative/attributes.rb +147 -0
- data/lib/anise/annotative/methods.rb +131 -0
- data/lib/anise/annotative/variables.rb +99 -0
- data/lib/anise/{module.rb → core_ext.rb} +30 -0
- data/lib/anise/universal.rb +6 -0
- data/lib/anise/version.rb +17 -0
- data/test/case_annotations.rb +173 -0
- data/test/case_attributes.rb +46 -0
- data/test/case_combined_usage.rb +341 -0
- data/test/case_methods.rb +36 -0
- data/test/case_variables.rb +22 -0
- data/test/helper.rb +2 -0
- metadata +99 -98
- data/APACHE2.txt +0 -204
- data/COPYING.rdoc +0 -17
- data/README.rdoc +0 -107
- data/lib/anise/annotation.rb +0 -175
- data/lib/anise/annotator.rb +0 -82
- data/lib/anise/attribute.rb +0 -138
- data/qed/01_annotations.qed +0 -26
- data/qed/02_annotation_added.rdoc +0 -60
- data/qed/03_attributes.rdoc +0 -16
- data/qed/04_annotator.rdoc +0 -49
- data/qed/toplevel/03_attributes.rdoc +0 -20
- data/test/suite.rb +0 -8
- data/test/test_anise.rb +0 -193
- data/test/test_anise_toplevel.rb +0 -194
- data/test/test_annotations.rb +0 -136
- data/test/test_annotations_module.rb +0 -132
- data/test/test_annotations_toplevel.rb +0 -131
- data/test/test_annotator.rb +0 -26
- data/test/test_annotator_toplevel.rb +0 -28
- data/test/test_attribute.rb +0 -37
- data/test/test_attribute_toplevel.rb +0 -65
@@ -0,0 +1,131 @@
|
|
1
|
+
module Anise
|
2
|
+
|
3
|
+
module Annotative
|
4
|
+
|
5
|
+
# TODO: Ensure thread-safety of <code>@_pending_annotations</code> variable.
|
6
|
+
|
7
|
+
# The Annotator::Method module allows for the creation of annotations
|
8
|
+
# which attach to the next method defined.
|
9
|
+
#
|
10
|
+
# This idiom of annotation-before-definition was popularized by Rake's
|
11
|
+
# `desc`/`task` pair. This module can be used to add similar capabilites
|
12
|
+
# to any class or module.
|
13
|
+
#
|
14
|
+
# class X
|
15
|
+
# extend Anise::Annotative::Methods
|
16
|
+
#
|
17
|
+
# def self.doc(string)
|
18
|
+
# method_annotation(:doc => string)
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# doc "See what I mean?"
|
22
|
+
#
|
23
|
+
# def see
|
24
|
+
# puts "Yes, I see!"
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# X.ann(:see, :doc) #=> "See what I mean?"
|
29
|
+
#
|
30
|
+
# One can get a bit more control over the creation of annotations
|
31
|
+
# by using a block. In this case it is up the code to actually
|
32
|
+
# create the annotation.
|
33
|
+
#
|
34
|
+
# def self.doc(string)
|
35
|
+
# method_annotation do |meth|
|
36
|
+
# ann meth, :doc => string
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# Note that the library uses the #method_added callback, so be sure to
|
41
|
+
# respect good practices of calling +super+ if you need to override
|
42
|
+
# this method.
|
43
|
+
#
|
44
|
+
module Methods
|
45
|
+
|
46
|
+
include Annotations
|
47
|
+
|
48
|
+
#
|
49
|
+
# This a temporary store used to create method annotations.
|
50
|
+
#
|
51
|
+
def self.pending_annotations
|
52
|
+
@_pending_annotations ||= Hash.new{ |h,k| h[k] = [] }
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Define a method annotation.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# method_annotator :doc
|
60
|
+
#
|
61
|
+
# @param name [Symbol]
|
62
|
+
# Name of annotation.
|
63
|
+
#
|
64
|
+
def method_annotator(name, &block)
|
65
|
+
(class << self; self; end).module_eval do
|
66
|
+
define_method(name) do |*args|
|
67
|
+
anns = { name => (args.size > 1 ? args : args.first) }
|
68
|
+
Methods.pending_annotations[self] << [anns, block]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
#
|
75
|
+
#
|
76
|
+
def annotator(name, &block)
|
77
|
+
if name.to_s.start_with?('@')
|
78
|
+
if defined?(super)
|
79
|
+
super(name, &block)
|
80
|
+
else
|
81
|
+
raise ArgumentError, "not a valid method name -- #{name}"
|
82
|
+
end
|
83
|
+
else
|
84
|
+
method_annotator(name, &block)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Setup a pending method annotation.
|
90
|
+
#
|
91
|
+
# @param [Hash] annotations
|
92
|
+
# The annotation settings.
|
93
|
+
#
|
94
|
+
def method_annotation(*args, &block)
|
95
|
+
anns = (Hash === args.last ? args.pop : {})
|
96
|
+
Methods.pending_annotations[self] << [anns, block]
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# When a method is added, run all pending annotations.
|
101
|
+
#
|
102
|
+
# @param [Symbol] sym
|
103
|
+
# The name of the method added.
|
104
|
+
#
|
105
|
+
def method_added(sym)
|
106
|
+
annotations = Methods.pending_annotations[self]
|
107
|
+
annotations.each do |anns, block|
|
108
|
+
if block
|
109
|
+
block.call(sym)
|
110
|
+
else
|
111
|
+
anns.each do |name, value|
|
112
|
+
if name.to_s.index('/')
|
113
|
+
name, ns = name.to_s.split('/')
|
114
|
+
else
|
115
|
+
ns = :ann
|
116
|
+
end
|
117
|
+
ann(sym/ns, name=>value)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
Methods.pending_annotations[self] = []
|
122
|
+
super if defined?(super)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
# Copyright (c) 2006 Rubyworks. All rights reserved. (BSD-2-Clause License)
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Anise
|
2
|
+
|
3
|
+
module Annotative
|
4
|
+
|
5
|
+
# I bet you never imagined Ruby could suport `@style` annotations.
|
6
|
+
# Well, I am here to tell you otherwise.
|
7
|
+
#
|
8
|
+
# The {VariableAnnotator} module allows class instance variable to be
|
9
|
+
# used method annotations which attach to the next defined method.
|
10
|
+
#
|
11
|
+
# class X
|
12
|
+
# extend Anise::Annotative::Variables
|
13
|
+
#
|
14
|
+
# variable_annotator :@doc
|
15
|
+
# variable_annotator :@returns
|
16
|
+
#
|
17
|
+
# @doc = "See what I mean?"
|
18
|
+
# @returns = NilClass
|
19
|
+
#
|
20
|
+
# def see
|
21
|
+
# puts "Yes, I see!"
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# X.ann(:see, :@doc) #=> "See what I mean?"
|
26
|
+
#
|
27
|
+
# This library uses the #method_added callback, so be sure to respect
|
28
|
+
# good practices of calling +super+ if you need to override this method.
|
29
|
+
#
|
30
|
+
# **IMPORTANT!!!** This library is an interesting expirement, but it remains
|
31
|
+
# to be determined if it makes sense for general use.
|
32
|
+
#
|
33
|
+
module Variables
|
34
|
+
|
35
|
+
include Annotations
|
36
|
+
|
37
|
+
#
|
38
|
+
# Open method annotations.
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# variable_annotator :@doc
|
42
|
+
#
|
43
|
+
# @param ns [Symbol]
|
44
|
+
# Annotator to use. Default is `:ann`.
|
45
|
+
#
|
46
|
+
def variable_annotator(iv, &block)
|
47
|
+
# TODO: should none iv raise an error instead?
|
48
|
+
iv = "@#{iv}".to_sym if iv.to_s !~ /^@/
|
49
|
+
|
50
|
+
# TODO: use an annotation to record the annotators
|
51
|
+
#ann(:variable_annotator, iv=>block)
|
52
|
+
|
53
|
+
@_variable_annotations ||= {}
|
54
|
+
@_variable_annotations[iv] = block
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
def annotator(iv, &block)
|
59
|
+
if not iv.to_s.start_with?('@')
|
60
|
+
if defined?(super)
|
61
|
+
super(iv, &block)
|
62
|
+
else
|
63
|
+
raise ArgumentError, "not a valid instance variable -- #{iv}"
|
64
|
+
end
|
65
|
+
else
|
66
|
+
variable_annotator(iv, ns, &block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# When a method is added, run all pending annotations.
|
72
|
+
#
|
73
|
+
def method_added(sym)
|
74
|
+
@_variable_annotations ||= {}
|
75
|
+
@_variable_annotations.each do |iv, block|
|
76
|
+
if iv.to_s.index('/')
|
77
|
+
iv, ns = iv.to_s.split('/')
|
78
|
+
else
|
79
|
+
ns = :ann
|
80
|
+
end
|
81
|
+
value = instance_variable_get(iv)
|
82
|
+
if block
|
83
|
+
block.call(sym, value)
|
84
|
+
else
|
85
|
+
ann(sym/ns, iv=>value)
|
86
|
+
end
|
87
|
+
# TODO: can we undefine the instance variable?
|
88
|
+
instance_variable_set(iv, nil)
|
89
|
+
end
|
90
|
+
super(sym) if defined?(super)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
# Copyright (c) 2006 Rubyworks. All rights reserved. (BSD-2-Clause License)
|
@@ -1,7 +1,11 @@
|
|
1
|
+
#require 'facets/inheritor' # removed dependency
|
2
|
+
|
1
3
|
class Module
|
4
|
+
#
|
2
5
|
# Module extension to return attribute methods. These are all methods
|
3
6
|
# that start with `attr_`. This method can be overriden in special cases
|
4
7
|
# to work with attribute annotations.
|
8
|
+
#
|
5
9
|
def attribute_methods
|
6
10
|
list = []
|
7
11
|
public_methods(true).each do |m|
|
@@ -17,3 +21,29 @@ class Module
|
|
17
21
|
end
|
18
22
|
end
|
19
23
|
|
24
|
+
class Symbol
|
25
|
+
#
|
26
|
+
# Create new combination symbol with slash.
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# :foo/:bar #=> :'foo/bar'
|
30
|
+
#
|
31
|
+
def /(other)
|
32
|
+
"#{self}/#{other}".to_sym
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class String
|
37
|
+
#
|
38
|
+
# Create new combination string with slash.
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# 'foo'/'bar' #=> 'foo/bar'
|
42
|
+
#
|
43
|
+
def /(other)
|
44
|
+
"#{self}/#{other}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Copyright (c) 2006 Rubyworks. All rights reserved. (BSD-2-Clause License)
|
49
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Anise
|
2
|
+
|
3
|
+
#
|
4
|
+
def self.metadata
|
5
|
+
@metadata ||= (
|
6
|
+
require 'yaml'
|
7
|
+
YAML.load(File.new(File.dirname(__FILE__) + '/../anise.yml'))
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
def self.const_missing(name)
|
13
|
+
metadata[name.to_s.downcase] || super(name)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,173 @@
|
|
1
|
+
testcase Anise::Annotations do
|
2
|
+
|
3
|
+
context "annotations can be defined" do
|
4
|
+
|
5
|
+
cX = Class.new do
|
6
|
+
extend Anise::Annotations
|
7
|
+
def x1 ; end
|
8
|
+
ann :x1, :a=>1
|
9
|
+
ann :x1, :b=>2
|
10
|
+
end
|
11
|
+
|
12
|
+
test "01" do
|
13
|
+
cX.ann(:x1,:a) == cX.ann(:x1,:a)
|
14
|
+
end
|
15
|
+
|
16
|
+
test "02" do
|
17
|
+
cX.ann(:x1,:a).object_id == cX.ann(:x1,:a).object_id
|
18
|
+
end
|
19
|
+
|
20
|
+
test "03" do
|
21
|
+
cX.ann(:x1,:a) == 1
|
22
|
+
end
|
23
|
+
|
24
|
+
test "04" do
|
25
|
+
cX.ann :x1, :a => 2
|
26
|
+
cX.ann(:x1,:a) == 2
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
context "parent annotations pass to subclass" do
|
32
|
+
|
33
|
+
cX = Class.new do
|
34
|
+
extend Anise::Annotations
|
35
|
+
def x1 ; end
|
36
|
+
ann :x1, :a=>1
|
37
|
+
ann :x1, :b=>2
|
38
|
+
end
|
39
|
+
|
40
|
+
cY = Class.new(cX)
|
41
|
+
|
42
|
+
test "01" do
|
43
|
+
cY.ann(:x1,:a) == cY.ann(:x1,:a)
|
44
|
+
end
|
45
|
+
|
46
|
+
test "02" do
|
47
|
+
cY.ann(:x1,:a).object_id == cY.ann(:x1,:a).object_id
|
48
|
+
end
|
49
|
+
|
50
|
+
test "03" do
|
51
|
+
cY.ann(:x1,:a) == 1
|
52
|
+
end
|
53
|
+
|
54
|
+
test "04" do
|
55
|
+
cY.ann(:x1,:b) == 2
|
56
|
+
end
|
57
|
+
|
58
|
+
test "05" do
|
59
|
+
cY.ann :x1,:a => 2
|
60
|
+
cY.ann(:x1,:a) == 2 &&
|
61
|
+
cY.ann(:x1,:b) == 2
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
context "subclass can override parent annotation" do
|
67
|
+
|
68
|
+
cX = Class.new do
|
69
|
+
extend Anise::Annotations
|
70
|
+
ann :foo, Integer
|
71
|
+
end
|
72
|
+
|
73
|
+
cY = Class.new(cX) do
|
74
|
+
ann :foo, String
|
75
|
+
end
|
76
|
+
|
77
|
+
test "01" do
|
78
|
+
cX.ann(:foo, :class) == Integer
|
79
|
+
end
|
80
|
+
|
81
|
+
test "02" do
|
82
|
+
cY.ann(:foo, :class) == String
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
context "subclass can override while parent also passes thru" do
|
88
|
+
|
89
|
+
cX = Class.new do
|
90
|
+
extend Anise::Annotations
|
91
|
+
ann :foo, :doc => "hello"
|
92
|
+
ann :foo, :bar => []
|
93
|
+
end
|
94
|
+
|
95
|
+
cY = Class.new(cX) do
|
96
|
+
ann :foo, :class=>String, :doc=>"bye"
|
97
|
+
end
|
98
|
+
|
99
|
+
test "01" do
|
100
|
+
cX.ann(:foo,:doc) == "hello"
|
101
|
+
end
|
102
|
+
|
103
|
+
test "02" do
|
104
|
+
cX.ann(:foo) == {:doc=>"hello", :bar=>[]}
|
105
|
+
end
|
106
|
+
|
107
|
+
test "03" do
|
108
|
+
cX.ann(:foo,:bar) << "1"
|
109
|
+
cX.ann(:foo,:bar) == ["1"]
|
110
|
+
end
|
111
|
+
|
112
|
+
test "04" do
|
113
|
+
cY.ann(:foo,:doc) == "bye"
|
114
|
+
end
|
115
|
+
|
116
|
+
test "05" do
|
117
|
+
#Y.ann(:foo,:bar) == nil
|
118
|
+
cY.ann(:foo,:bar) == ["1"]
|
119
|
+
end
|
120
|
+
|
121
|
+
test "06" do
|
122
|
+
cY.ann(:foo, :doc => "cap")
|
123
|
+
cY.ann(:foo, :doc) == "cap"
|
124
|
+
end
|
125
|
+
|
126
|
+
test "07" do
|
127
|
+
cY.ann!(:foo,:bar) << "2"
|
128
|
+
|
129
|
+
cY.ann(:foo,:bar) == ["1", "2"] &&
|
130
|
+
cY.ann(:foo,:bar) == ["1", "2"] &&
|
131
|
+
cX.ann(:foo,:bar) == ["1"]
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
context "example of using annotations for validation" do
|
137
|
+
|
138
|
+
cX = Class.new do
|
139
|
+
extend Anise::Annotations
|
140
|
+
|
141
|
+
attr :a
|
142
|
+
|
143
|
+
ann :@a, :valid => lambda{ |x| x.is_a?(Integer) }
|
144
|
+
ann :a, :class => Integer
|
145
|
+
|
146
|
+
def initialize(a)
|
147
|
+
@a = a
|
148
|
+
end
|
149
|
+
|
150
|
+
def validate
|
151
|
+
instance_variables.each do |iv|
|
152
|
+
if validator = self.class.ann(iv)[:valid]
|
153
|
+
value = instance_variable_get(iv)
|
154
|
+
unless validator.call(value)
|
155
|
+
raise "Invalid value #{value} for #{iv}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
test "annotation class" do
|
163
|
+
cX.ann(:a, :class) == Integer
|
164
|
+
end
|
165
|
+
|
166
|
+
test "annotation validate" do
|
167
|
+
r = cX.new(1)
|
168
|
+
r.validate
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|