anise 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.ruby +59 -38
  2. data/.yardopts +7 -0
  3. data/DEMO.md +242 -0
  4. data/{HISTORY.rdoc → HISTORY.md} +28 -7
  5. data/LICENSE.txt +27 -0
  6. data/README.md +129 -0
  7. data/demo/01_annotations.md +81 -0
  8. data/demo/03_attributes.md +14 -0
  9. data/demo/04_methods.md +50 -0
  10. data/demo/05_variables.md +45 -0
  11. data/{qed → demo}/applique/ae.rb +0 -0
  12. data/demo/applique/anise.rb +1 -0
  13. data/{qed/toplevel/01_annotations.qed → demo/toplevel/01_annotations.md} +5 -9
  14. data/demo/toplevel/03_attributes.md +20 -0
  15. data/lib/anise.rb +28 -45
  16. data/lib/anise.yml +59 -38
  17. data/lib/anise/annotations.rb +132 -0
  18. data/lib/anise/annotations/store.rb +136 -0
  19. data/lib/anise/annotative.rb +7 -0
  20. data/lib/anise/annotative/attributes.rb +147 -0
  21. data/lib/anise/annotative/methods.rb +131 -0
  22. data/lib/anise/annotative/variables.rb +99 -0
  23. data/lib/anise/{module.rb → core_ext.rb} +30 -0
  24. data/lib/anise/universal.rb +6 -0
  25. data/lib/anise/version.rb +17 -0
  26. data/test/case_annotations.rb +173 -0
  27. data/test/case_attributes.rb +46 -0
  28. data/test/case_combined_usage.rb +341 -0
  29. data/test/case_methods.rb +36 -0
  30. data/test/case_variables.rb +22 -0
  31. data/test/helper.rb +2 -0
  32. metadata +99 -98
  33. data/APACHE2.txt +0 -204
  34. data/COPYING.rdoc +0 -17
  35. data/README.rdoc +0 -107
  36. data/lib/anise/annotation.rb +0 -175
  37. data/lib/anise/annotator.rb +0 -82
  38. data/lib/anise/attribute.rb +0 -138
  39. data/qed/01_annotations.qed +0 -26
  40. data/qed/02_annotation_added.rdoc +0 -60
  41. data/qed/03_attributes.rdoc +0 -16
  42. data/qed/04_annotator.rdoc +0 -49
  43. data/qed/toplevel/03_attributes.rdoc +0 -20
  44. data/test/suite.rb +0 -8
  45. data/test/test_anise.rb +0 -193
  46. data/test/test_anise_toplevel.rb +0 -194
  47. data/test/test_annotations.rb +0 -136
  48. data/test/test_annotations_module.rb +0 -132
  49. data/test/test_annotations_toplevel.rb +0 -131
  50. data/test/test_annotator.rb +0 -26
  51. data/test/test_annotator_toplevel.rb +0 -28
  52. data/test/test_attribute.rb +0 -37
  53. data/test/test_attribute_toplevel.rb +0 -65
@@ -1,41 +1,62 @@
1
- ---
2
- spec_version: 1.0.0
3
- replaces: []
4
-
5
- loadpath:
6
- - lib
7
- name: anise
8
- repositories: {}
9
-
10
- conflicts: []
11
-
12
- engine_check: []
13
-
14
- title: Anise
15
- contact: trans <transfire@gmail.com>
16
- resources:
17
- code: http://github.com/rubyworks/anise
18
- mail: http://groups.google.com/group/rubyworks-mailinglist
19
- home: http://rubyworks.github.com/anise
20
- maintainers: []
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
- name: qed
26
- version: 0+
27
- - group:
23
+ development: true
24
+ - name: detroit
25
+ groups:
28
26
  - build
29
- name: redline
30
- version: 0+
31
- manifest: MANIFEST
32
- version: 0.6.0
33
- licenses:
34
- - Apache 2.0
35
- copyright: Copyright (c) 2008 Thomas Sawyer
36
- authors:
37
- - Thomas Sawyer
38
- organization: Rubyworks
39
- description: Anise is an Annotation System for the Ruby programming language. Unlike most other annotations systems it is not a comment-based or macro-based system that sits over-and-above the rest of the code. Rather, Anise is a dynamic annotations system operating at runtime.
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
- created: "2008-02-21"
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,7 @@
1
+ module Anise
2
+
3
+ module Annotative
4
+
5
+ end
6
+
7
+ 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)