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.
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)