anise 0.6.0 → 0.7.0
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/.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)
|