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
data/lib/anise.yml
CHANGED
@@ -1,41 +1,62 @@
|
|
1
|
-
---
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
requires:
|
23
|
-
- group:
|
1
|
+
---
|
2
|
+
source:
|
3
|
+
- meta
|
4
|
+
authors:
|
5
|
+
- name: trans
|
6
|
+
email: transfire@gmail.com
|
7
|
+
copyrights:
|
8
|
+
- holder: Rubyworks
|
9
|
+
year: '2008'
|
10
|
+
license: BSD-2-Clause
|
11
|
+
requirements:
|
12
|
+
- name: qed
|
13
|
+
groups:
|
14
|
+
- test
|
15
|
+
development: true
|
16
|
+
- name: ae
|
17
|
+
groups:
|
18
|
+
- test
|
19
|
+
development: true
|
20
|
+
- name: citron
|
21
|
+
groups:
|
24
22
|
- test
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
development: true
|
24
|
+
- name: detroit
|
25
|
+
groups:
|
28
26
|
- build
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
27
|
+
development: true
|
28
|
+
dependencies: []
|
29
|
+
alternatives: []
|
30
|
+
conflicts: []
|
31
|
+
repositories:
|
32
|
+
- uri: http://github.com/rubyworks/anise.git
|
33
|
+
scm: git
|
34
|
+
name: public
|
35
|
+
resources:
|
36
|
+
- uri: http://rubyworks.githuib.com/anise
|
37
|
+
name: home
|
38
|
+
type: home
|
39
|
+
- uri: http://github.com/rubyworks/anise
|
40
|
+
name: code
|
41
|
+
type: code
|
42
|
+
- uri: http://github.com/rubyworks/anise/issues
|
43
|
+
name: bugs
|
44
|
+
type: bugs
|
45
|
+
- uri: http://chat.us.freenode.net/rubyworks
|
46
|
+
name: chat
|
47
|
+
type: chat
|
48
|
+
- uri: http://groups.google.com/groups/rubyworks-mailinglist
|
49
|
+
name: mail
|
50
|
+
type: mail
|
51
|
+
extra: {}
|
52
|
+
load_path:
|
53
|
+
- lib
|
54
|
+
revision: 0
|
55
|
+
created: '2008-02-21'
|
40
56
|
summary: Dynamic Annotation System
|
41
|
-
|
57
|
+
title: Anise
|
58
|
+
version: 0.7.0
|
59
|
+
name: anise
|
60
|
+
description: Anise is an annotations systems for the Ruby programming lanaguage.
|
61
|
+
organization: Rubyworks
|
62
|
+
date: '2012-04-20'
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Anise
|
2
|
+
|
3
|
+
# TODO: The ann(x).name notation is kind of nice. Would like to add that
|
4
|
+
# back-in if reasonable.
|
5
|
+
|
6
|
+
# The Annotation provides a framework for annotating class and module related
|
7
|
+
# objects, typically symbols representing methods, with arbitrary metadata.
|
8
|
+
# These annotations do not do anything in themselves. They are simply data.
|
9
|
+
# But they can be put to good use. For instance an attribute validator might
|
10
|
+
# check for an annotation called :valid and test against it.
|
11
|
+
#
|
12
|
+
# The standard annotator is `:ann` and is the defualt value of annotating
|
13
|
+
# methods.
|
14
|
+
#
|
15
|
+
# class X
|
16
|
+
# extend Anise::Annotations
|
17
|
+
#
|
18
|
+
# ann :a, :desc => "A Number"
|
19
|
+
#
|
20
|
+
# attr :a
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# X.ann(:a, :desc) #=> "A Number"
|
24
|
+
#
|
25
|
+
# As stated, annotations need not only annotate methods, they are
|
26
|
+
# arbitrary, so they can be used for any purpose. For example, we
|
27
|
+
# may want to annotate instance variables.
|
28
|
+
#
|
29
|
+
# class X
|
30
|
+
# ann :@a, :valid => lambda{ |x| x.is_a?(Integer) }
|
31
|
+
#
|
32
|
+
# def validate
|
33
|
+
# instance_variables.each do |iv|
|
34
|
+
# if validator = self.class.ann(iv)[:valid]
|
35
|
+
# value = instance_variable_get(iv)
|
36
|
+
# unless validator.call(value)
|
37
|
+
# raise "Invalid value #{value} for #{iv}"
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# Or, we could even annotate the class itself.
|
45
|
+
#
|
46
|
+
# class X
|
47
|
+
# ann self, :valid => lambda{ |x| x.is_a?(Enumerable) }
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# Although annotations are arbitrary they are tied to the class or
|
51
|
+
# module they are defined against.
|
52
|
+
#
|
53
|
+
# Creating custom annotators used to entail using a special `#annotator` method,
|
54
|
+
# but this limited the way custom annotators could operate. The new way
|
55
|
+
# is to define a custom class method that calls the usual `#ann` method,
|
56
|
+
# but add in a namespace.
|
57
|
+
#
|
58
|
+
# class X
|
59
|
+
# def self.cool(ref, *keys)
|
60
|
+
# ann "#{ref}/cool", *keys
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# X.cool(:a, :desc) #=> "Awesome!"
|
65
|
+
#
|
66
|
+
# The result is exactly the same as before, but now the custom annotator
|
67
|
+
# has complete control over the process.
|
68
|
+
#
|
69
|
+
module Annotations
|
70
|
+
|
71
|
+
#
|
72
|
+
# Access to a class or module's annotations.
|
73
|
+
#
|
74
|
+
def annotations
|
75
|
+
@annotations ||= Store.new(self)
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Callback method. This method is called for each new annotation.
|
80
|
+
#
|
81
|
+
def annotation_added(ref, ns=:ann)
|
82
|
+
super(ref, ns) if defined?(super)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Get/set annotations.
|
86
|
+
#
|
87
|
+
# @examples
|
88
|
+
# ann :ref, :key=>value
|
89
|
+
# ann :ref/:ns, :key=>value
|
90
|
+
#
|
91
|
+
# ann :ref, :key
|
92
|
+
# ann :ref/:ns, :key
|
93
|
+
#
|
94
|
+
def ann(ref, *keys)
|
95
|
+
if ref.to_s.index('/')
|
96
|
+
ref, ns = ref.to_s.split('/')
|
97
|
+
else
|
98
|
+
ns = :ann
|
99
|
+
end
|
100
|
+
ref, ns = ref.to_sym, ns.to_sym
|
101
|
+
|
102
|
+
if keys.empty?
|
103
|
+
annotations.lookup(ref, ns)
|
104
|
+
else
|
105
|
+
annotations.annotate(ns, ref, *keys)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# Get/set annotations in-place. Use this method instead of `#ann`
|
111
|
+
# when performing mass updates.
|
112
|
+
#
|
113
|
+
def ann!(ref, *keys)
|
114
|
+
if ref.to_s.index('/')
|
115
|
+
ref, ns = ref.to_s.split('/')
|
116
|
+
else
|
117
|
+
ns = :ann
|
118
|
+
end
|
119
|
+
ref, ns = ref.to_sym, ns.to_sym
|
120
|
+
|
121
|
+
if keys.empty?
|
122
|
+
annotations[ref, ns]
|
123
|
+
else
|
124
|
+
annotations.annotate!(ns, ref, *keys)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
# Copyright (c) 2006 Rubyworks. All rights reserved. (BSD-2-Clause License)
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module Anise
|
2
|
+
|
3
|
+
module Annotations
|
4
|
+
|
5
|
+
# The {Annotations::Store} class tracks annotations on a per-class bases.
|
6
|
+
#
|
7
|
+
class Store
|
8
|
+
|
9
|
+
# Setup new Annotations instance.
|
10
|
+
#
|
11
|
+
# @param space [Class,Module]
|
12
|
+
# Class or Module to have annotations.
|
13
|
+
#
|
14
|
+
def initialize(space)
|
15
|
+
@space = space
|
16
|
+
@table = Hash.new { |h,k| h[k]={} }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Ancestors of spaceal class/module.
|
20
|
+
def ancestors
|
21
|
+
@space.ancestors
|
22
|
+
end
|
23
|
+
|
24
|
+
# Annotations local to spaceal class/module.
|
25
|
+
def local
|
26
|
+
@table
|
27
|
+
end
|
28
|
+
|
29
|
+
# Access to local table.
|
30
|
+
def [](ref, ns=:ann)
|
31
|
+
@table[ns][ref]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Lookup an annotation. Unlike `self[ref]`
|
35
|
+
# this provides a complete annotation _heritage_,
|
36
|
+
# pulling annotations of the same reference name
|
37
|
+
# from ancestor classes and modules.
|
38
|
+
#
|
39
|
+
# Unlike the other annotation methods, this method takes
|
40
|
+
# the `ref` argument before the `ns` argument. This is
|
41
|
+
# it allow `ns` to default to the common annotator `ann`.
|
42
|
+
#
|
43
|
+
# @param ref [Object] Annotation reference key.
|
44
|
+
#
|
45
|
+
# @param ns [Symbol] Annotation namespace.
|
46
|
+
#
|
47
|
+
def lookup(ref=nil, ns=:ann)
|
48
|
+
return @table if ref.nil?
|
49
|
+
|
50
|
+
ref, ns = ref.to_sym, (ns || :ann).to_sym
|
51
|
+
|
52
|
+
ann = {}
|
53
|
+
ancestors.reverse_each do |anc|
|
54
|
+
next unless anc.is_a?(Annotations)
|
55
|
+
if h = anc.annotations.local[ns][ref]
|
56
|
+
ann.merge!(h)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
return ann
|
60
|
+
end
|
61
|
+
|
62
|
+
# Set or read annotations.
|
63
|
+
#
|
64
|
+
# IMPORTANT! Do not use this for in-place modifications.
|
65
|
+
# Use #annotate! instead.
|
66
|
+
#
|
67
|
+
# @pararm ns [Symbol] Annotation namespace.
|
68
|
+
#
|
69
|
+
# @param ref [Object] Annotation reference key.
|
70
|
+
#
|
71
|
+
# @since 0.7.0
|
72
|
+
def annotate(ns, ref, keys_or_class, keys=nil)
|
73
|
+
if Class === keys_or_class
|
74
|
+
keys ||= {}
|
75
|
+
keys[:class] = keys_or_class
|
76
|
+
else
|
77
|
+
keys = keys_or_class
|
78
|
+
end
|
79
|
+
|
80
|
+
if Hash === keys
|
81
|
+
update(ns, ref, keys)
|
82
|
+
else
|
83
|
+
key = keys.to_sym
|
84
|
+
lookup(ref, ns)[key]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# To change an annotation's value in place for a given class or module
|
89
|
+
# it first must be duplicated, otherwise the change may effect annotations
|
90
|
+
# in the class or module's ancestors.
|
91
|
+
#
|
92
|
+
# @pararm ns [Symbol] Annotation namespace.
|
93
|
+
#
|
94
|
+
# @param ref [Object] Annotation reference key.
|
95
|
+
#
|
96
|
+
# @since 0.7.0
|
97
|
+
def annotate!(ns, ref, keys_or_class, keys=nil)
|
98
|
+
if Class === keys_or_class
|
99
|
+
keys ||= {}
|
100
|
+
keys[:class] = keys_or_class
|
101
|
+
else
|
102
|
+
keys = keys_or_class
|
103
|
+
end
|
104
|
+
|
105
|
+
if Hash === keys
|
106
|
+
update(ns, ref, keys)
|
107
|
+
else
|
108
|
+
key = keys.to_sym
|
109
|
+
@table[ns][ref] ||= {}
|
110
|
+
begin
|
111
|
+
@table[ns][ref][key] = lookup(ref, ns)[key].dup
|
112
|
+
rescue TypeError
|
113
|
+
@table[ns][ref][key] = lookup(ref, ns)[key]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Update annotations for a given namespace and reference.
|
119
|
+
def update(ns, ref, hash)
|
120
|
+
ref = ref.to_sym
|
121
|
+
|
122
|
+
@table[ns][ref] ||= {}
|
123
|
+
|
124
|
+
hash.each do |k,v|
|
125
|
+
@table[ns][ref][k.to_sym] = v
|
126
|
+
end
|
127
|
+
|
128
|
+
# callback
|
129
|
+
@space.annotation_added(ref, ns) #if method_defined?(:annotation_added)
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Anise
|
2
|
+
|
3
|
+
module Annotative
|
4
|
+
|
5
|
+
# The {Annotative::Attributes} mixin modifies the attr_* methods to allow easy
|
6
|
+
# addition of annotations for attributes. It modifies the built in
|
7
|
+
# attribute methods (attr, attr_reader, attr_writer and attr_accessor),
|
8
|
+
# and any other custom `attr_*` methods, to allow annotations to be
|
9
|
+
# added to them directly rather than requiring a separate annotating
|
10
|
+
# statement.
|
11
|
+
#
|
12
|
+
# class X
|
13
|
+
# extend Anise::Annotative::Attributes
|
14
|
+
#
|
15
|
+
# attr :a, :valid => lambda{ |x| x.is_a?(Integer) }
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# See {Annotation} module for more information.
|
19
|
+
#
|
20
|
+
# @todo Currently annotated attributes alwasy use the standard
|
21
|
+
# annotator (:ann). In the future we might make this customizable.
|
22
|
+
#
|
23
|
+
module Attributes
|
24
|
+
|
25
|
+
include Annotations
|
26
|
+
|
27
|
+
#
|
28
|
+
# When included into a class or module, {Annotation} is also
|
29
|
+
# included and {Attribute::Aid} extends the class/module.
|
30
|
+
#
|
31
|
+
# @param base [Class, Module]
|
32
|
+
# The class or module to get features.
|
33
|
+
#
|
34
|
+
def self.extended(base)
|
35
|
+
#inheritor :instance_attributes, [], :|
|
36
|
+
base_class = (class << base; self; end)
|
37
|
+
#base_class.attribute_methods.each do |attr_method|
|
38
|
+
base.attribute_methods.each do |attr_method|
|
39
|
+
define_annotated_attribute(base_class, attr_method)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# TODO: Might #define_annotated_attribute make an acceptable class extension?
|
44
|
+
|
45
|
+
#
|
46
|
+
# Define an annotated attribute method, given an existing
|
47
|
+
# non-annotated attribute method.
|
48
|
+
#
|
49
|
+
def self.define_annotated_attribute(base, attr_method_name)
|
50
|
+
base.module_eval do
|
51
|
+
define_method(attr_method_name) do |*args|
|
52
|
+
args.flatten!
|
53
|
+
|
54
|
+
harg={}; while args.last.is_a?(Hash)
|
55
|
+
harg.update(args.pop)
|
56
|
+
end
|
57
|
+
|
58
|
+
raise ArgumentError if args.empty? and harg.empty?
|
59
|
+
|
60
|
+
if args.empty? # hash mode
|
61
|
+
harg.each { |a,h| __send__(attr_method_name,a,h) }
|
62
|
+
else
|
63
|
+
klass = harg[:class] = args.pop if args.last.is_a?(Class)
|
64
|
+
|
65
|
+
super(*args) #attr_method.call(*args)
|
66
|
+
|
67
|
+
args.each{|a| ann(a.to_sym,harg)}
|
68
|
+
|
69
|
+
instance_attributes!.concat(args) #merge!
|
70
|
+
|
71
|
+
# Use this callback to customize for your needs.
|
72
|
+
if respond_to?(:attr_callback)
|
73
|
+
attr_callback(self, args, harg)
|
74
|
+
end
|
75
|
+
|
76
|
+
# return the names of the attributes created
|
77
|
+
return args
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# Instance attributes, including inherited attributes.
|
85
|
+
#
|
86
|
+
def instance_attributes
|
87
|
+
a = []
|
88
|
+
ancestors.each do |anc|
|
89
|
+
next unless anc < Attributes
|
90
|
+
if x = anc.instance_attributes!
|
91
|
+
a |= x
|
92
|
+
end
|
93
|
+
end
|
94
|
+
return a
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# Local instance attributes.
|
99
|
+
#
|
100
|
+
def instance_attributes!
|
101
|
+
@instance_attributes ||= []
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Return list of attributes that have a :class annotation.
|
106
|
+
#
|
107
|
+
# class MyClass
|
108
|
+
# attr_accessor :test
|
109
|
+
# attr_accessor :name, String, :doc => 'Hello'
|
110
|
+
# attr_accessor :age, Fixnum
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# MyClass.instance_attributes # => [:test, :name, :age, :body]
|
114
|
+
# MyClass.classified_attributes # => [:name, :age]
|
115
|
+
#
|
116
|
+
def classified_attributes
|
117
|
+
instance_attributes.find_all do |a|
|
118
|
+
self.ann(a, :class)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# This defines a simple adjustment to #attr to allow it to handle the boolean argument and
|
124
|
+
# to be able to accept attributes. It's backward compatible and is not needed for Ruby 1.9
|
125
|
+
# which gets rid of the secondary argument (or was suppose to!).
|
126
|
+
#
|
127
|
+
def attr(*args)
|
128
|
+
args.flatten!
|
129
|
+
case args.last
|
130
|
+
when TrueClass
|
131
|
+
args.pop
|
132
|
+
attr_accessor(*args)
|
133
|
+
when FalseClass, NilClass
|
134
|
+
args.pop
|
135
|
+
attr_reader(*args)
|
136
|
+
else
|
137
|
+
attr_reader(*args)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
# Copyright (c) 2006,2011 Thomas Sawyer. All rights reserved. (BSD-2-Clause License)
|