├── var ├── name ├── title ├── version ├── created ├── organizations ├── summary ├── authors ├── copyrights ├── repositories ├── description ├── requirements └── resources ├── lib ├── anise.yml ├── anise │ ├── annotative.rb │ ├── universal.rb │ ├── version.rb │ ├── core_ext.rb │ ├── annotative │ │ ├── variables.rb │ │ ├── methods.rb │ │ └── attributes.rb │ ├── annotations.rb │ └── annotations │ │ └── store.rb └── anise.rb ├── demo ├── applique │ ├── ae.rb │ └── anise.rb ├── 03_attributes.md ├── toplevel │ ├── 03_attributes.md │ └── 01_annotations.md ├── 05_variables.md ├── 04_methods.md └── 01_annotations.md ├── test ├── helper.rb ├── case_variables.rb ├── case_methods.rb ├── case_attributes.rb ├── case_annotations.rb └── case_combined_usage.rb ├── Gemfile ├── work ├── test │ └── toplevel │ │ ├── test_toplevel_method.rb │ │ ├── test_toplevel_attribute.rb │ │ ├── test_toplevel_annotation.rb │ │ └── test_anise_toplevel.rb ├── deprecated │ ├── PROFILE │ ├── test_annotator_module.rb │ ├── annotations0.rb │ ├── annattr.rb │ ├── annotations.rb │ └── annotation-old.rb ├── NOTES.rdoc ├── consider │ ├── annotations.rb │ └── attrait.rb ├── INDEX.md └── historic │ └── annotations.rb ├── .yardopts ├── .gitignore ├── Rakefile ├── etc └── test.rb ├── .travis.yml ├── LICENSE.txt ├── Assembly ├── .index ├── HISTORY.md ├── README.md └── .gemspec /var/name: -------------------------------------------------------------------------------- 1 | anise 2 | -------------------------------------------------------------------------------- /lib/anise.yml: -------------------------------------------------------------------------------- 1 | ../.ruby -------------------------------------------------------------------------------- /var/title: -------------------------------------------------------------------------------- 1 | Anise 2 | -------------------------------------------------------------------------------- /var/version: -------------------------------------------------------------------------------- 1 | 0.7.1 2 | -------------------------------------------------------------------------------- /var/created: -------------------------------------------------------------------------------- 1 | 2008-02-21 2 | -------------------------------------------------------------------------------- /demo/applique/ae.rb: -------------------------------------------------------------------------------- 1 | require 'ae/should' 2 | -------------------------------------------------------------------------------- /demo/applique/anise.rb: -------------------------------------------------------------------------------- 1 | require 'anise' 2 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'anise' 2 | 3 | -------------------------------------------------------------------------------- /var/organizations: -------------------------------------------------------------------------------- 1 | --- 2 | - Rubyworks 3 | -------------------------------------------------------------------------------- /var/summary: -------------------------------------------------------------------------------- 1 | Dynamic Annotations for Ruby 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /var/authors: -------------------------------------------------------------------------------- 1 | --- 2 | - trans 3 | -------------------------------------------------------------------------------- /work/test/toplevel/test_toplevel_method.rb: -------------------------------------------------------------------------------- 1 | # TODO 2 | -------------------------------------------------------------------------------- /var/copyrights: -------------------------------------------------------------------------------- 1 | --- 2 | - 2008 Rubyworks (BSD-2-Clause) 3 | -------------------------------------------------------------------------------- /var/repositories: -------------------------------------------------------------------------------- 1 | --- 2 | upstream: http://github.com/rubyworks/anise.git 3 | -------------------------------------------------------------------------------- /var/description: -------------------------------------------------------------------------------- 1 | Anise is an annotations systems for the Ruby programming lanaguage. 2 | -------------------------------------------------------------------------------- /lib/anise/annotative.rb: -------------------------------------------------------------------------------- 1 | module Anise 2 | 3 | module Annotative 4 | 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title "Anise" 2 | --output-dir doc 3 | --private 4 | --protected 5 | lib/ 6 | - 7 | [A-Z][A-Z]*.* 8 | -------------------------------------------------------------------------------- /lib/anise/universal.rb: -------------------------------------------------------------------------------- 1 | require 'anise' 2 | 3 | class Module 4 | include Anise::Annotations 5 | end 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.lock 3 | .ergo/digest 4 | DEMO.md 5 | log/ 6 | pkg/ 7 | tmp/ 8 | work/sandbox 9 | web/ 10 | 11 | 12 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/rnv ruby 2 | 3 | desc "run unit tests" 4 | task :test do 5 | sh "rubytest" 6 | end 7 | 8 | task :default => [:test] 9 | -------------------------------------------------------------------------------- /etc/test.rb: -------------------------------------------------------------------------------- 1 | Test.run do |run| 2 | require 'citron' 3 | require 'ae' 4 | 5 | require './test/helper.rb' 6 | 7 | run.files << 'test/*.rb' 8 | end 9 | 10 | -------------------------------------------------------------------------------- /var/requirements: -------------------------------------------------------------------------------- 1 | --- 2 | - detroit (build) 3 | - ergo (build) 4 | - qed (test) 5 | - ae (test) 6 | - citron (test) 7 | - rubytest (test) 8 | - rubytest-cli (test) 9 | 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | script: "bundle exec rubytest" 4 | rvm: 5 | - 1.8.7 6 | - 1.9.2 7 | - 1.9.3 8 | - rbx-18mode 9 | - rbx-19mode 10 | - jruby-18mode 11 | - jruby-19mode 12 | 13 | -------------------------------------------------------------------------------- /var/resources: -------------------------------------------------------------------------------- 1 | --- 2 | home: http://rubyworks.github.com/anise 3 | code: http://github.com/rubyworks/anise 4 | bugs: http://github.com/rubyworks/anise/issues 5 | chat: http://chat.us.freenode.net/rubyworks 6 | mail: http://groups.google.com/groups/rubyworks-mailinglist 7 | 8 | -------------------------------------------------------------------------------- /lib/anise/version.rb: -------------------------------------------------------------------------------- 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 | 18 | -------------------------------------------------------------------------------- /demo/03_attributes.md: -------------------------------------------------------------------------------- 1 | # Annotative Attributes 2 | 3 | Create a class that uses the `Annotative::Attributes` mixin. 4 | 5 | class X 6 | extend Anise::Annotative::Attributes 7 | 8 | attr :a, :count => 1 9 | end 10 | 11 | Then we can see tht the attribute method `:a` has an annotation entry. 12 | 13 | X.ann(:a, :count) #=> 1 14 | 15 | -------------------------------------------------------------------------------- /work/deprecated/PROFILE: -------------------------------------------------------------------------------- 1 | --- 2 | title : Anise 3 | suite : rubyworks 4 | summary : Dynamic Annotations System 5 | copyright : Copyright (c) 2008 Thomas Sawyer 6 | license : Ruby 7 | contact : rubyworks-mailinglist@googlegroups.com 8 | created : 2008-02-21 9 | 10 | authors: 11 | - Thomas Sawyer 12 | 13 | description: 14 | Anise is a runtime-baded annotations systems for the Ruby 15 | programming lanaguage. 16 | 17 | -------------------------------------------------------------------------------- /test/case_variables.rb: -------------------------------------------------------------------------------- 1 | testcase Anise::Annotative::Variables do 2 | 3 | context "general" do 4 | 5 | cX = Class.new do 6 | extend Anise::Annotative::Variables 7 | 8 | @doc = "See what I mean?" 9 | @returns = NilClass 10 | 11 | def see 12 | puts "Yes, I see!" 13 | end 14 | end 15 | 16 | test do 17 | cX.ann(:see, :@doc) == "See what I mean?" 18 | end 19 | 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /demo/toplevel/03_attributes.md: -------------------------------------------------------------------------------- 1 | = TOPLEVEL Annotated Attributes 2 | 3 | Including `AnnotatedAttributes` at the toplevel, i.e. Object, will make 4 | annotated attributes univerally available. 5 | 6 | class ::Object 7 | extend Anise::Annotative::Attributes 8 | end 9 | 10 | Create a class that uses it. 11 | 12 | class X 13 | attr :a, :count=>1 14 | end 15 | 16 | X.ann(:a, :count) #=> 1 17 | 18 | Alternatively the `Annotative::Attributes` module could be included into 19 | the `Module` class. 20 | 21 | -------------------------------------------------------------------------------- /test/case_methods.rb: -------------------------------------------------------------------------------- 1 | testcase Anise::Annotative::Methods do 2 | 3 | cX = Class.new do 4 | extend Anise::Annotative::Methods 5 | 6 | def self.req(val) 7 | method_annotation(:req=>val) 8 | end 9 | 10 | req 'r' 11 | def a ; "a"; end 12 | 13 | req ['x', 'y'] 14 | attr :b 15 | end 16 | 17 | test do |a, h| 18 | h.each do |k, r| 19 | cX.ann(a, k).assert == r 20 | end 21 | end 22 | 23 | ok :a, :req => 'r' 24 | ok :b, :req => ['x','y'] 25 | 26 | test do |h| 27 | h.each do |a, r| 28 | cX.ann(a).assert == r 29 | end 30 | end 31 | 32 | ok :a => {:req=>'r'} 33 | ok :b => {:req=>['x','y']} 34 | 35 | end 36 | 37 | -------------------------------------------------------------------------------- /demo/toplevel/01_annotations.md: -------------------------------------------------------------------------------- 1 | = TOPLEVEL Annotations 2 | 3 | Extending Object with `Annotations` should make them available to all classes. 4 | 5 | class ::Object 6 | extend Anise::Annotations 7 | end 8 | 9 | Given a example class X we can apply annotations to it using the #ann method. 10 | 11 | class X 12 | ann :x1, :a=>1 13 | ann :x1, :b=>2 14 | end 15 | 16 | We can then use #ann to lookup the set annotations. 17 | 18 | X.ann(:x1,:a).should == 1 19 | 20 | The #ann method is a public interface, so we can define annotation externally as well. 21 | 22 | X.ann :x1, :a => 2 23 | X.ann(:x1, :a).should == 2 24 | 25 | Alternatively the `Annotations` module could be included into the `Module` class. 26 | 27 | -------------------------------------------------------------------------------- /test/case_attributes.rb: -------------------------------------------------------------------------------- 1 | testcase Anise::Annotative::Attributes do 2 | 3 | context "general" do 4 | 5 | cX = Class.new do 6 | extend Anise::Annotative::Attributes 7 | attr :q 8 | attr :a, :x => 1 9 | end 10 | 11 | test "attr :a" do 12 | cX.ann(:a) == {:x=>1} 13 | end 14 | 15 | end 16 | 17 | context "attr" do 18 | 19 | cA = Class.new do 20 | extend Anise::Annotative::Attributes 21 | attr :x, :cast=>"to_s" 22 | end 23 | 24 | test do 25 | cA.ann(:x,:cast) == "to_s" 26 | end 27 | 28 | end 29 | 30 | context "attr_accessor" do 31 | 32 | cA = Class.new do 33 | extend Anise::Annotative::Attributes 34 | attr_accessor :x, :cast=>"to_s" 35 | end 36 | 37 | test do 38 | a = cA.new 39 | r = cA.instance_attributes - [:taguri] 40 | r == [:x] 41 | end 42 | 43 | end 44 | 45 | end 46 | 47 | -------------------------------------------------------------------------------- /demo/05_variables.md: -------------------------------------------------------------------------------- 1 | # Variable Annotations 2 | 3 | Create a class that uses the `Annotative::Variables` mixin. 4 | 5 | class X 6 | extend Anise::Annotative::Variables 7 | 8 | variable_annotator :@doc 9 | 10 | @doc = "See what I mean?" 11 | 12 | def see 13 | puts "Yes, I see!" 14 | end 15 | end 16 | 17 | See that it is set. 18 | 19 | X.ann(:see, :@doc).should == "See what I mean?" 20 | 21 | Variable annotations can override the standard annotation procedure with a 22 | custom procedure. 23 | 24 | class Y 25 | extend Anise::Annotative::Variables 26 | 27 | def self.list 28 | @list ||= [] 29 | end 30 | 31 | variable_annotator :@doc do |method, value| 32 | list << [method, value] 33 | end 34 | 35 | @doc = "See here!" 36 | 37 | def see 38 | puts "Yes, I see!" 39 | end 40 | end 41 | 42 | See that it is set. 43 | 44 | Y.list #=> [[:see, "See here!"]] 45 | 46 | -------------------------------------------------------------------------------- /lib/anise.rb: -------------------------------------------------------------------------------- 1 | # Dynamic Annotations for Ruby. 2 | # 3 | # require 'anise' 4 | # 5 | # Provides annotations: 6 | # 7 | # class X 8 | # extend Anise::Annotations 9 | # 10 | # ann :foo, :class=>String 11 | # end 12 | # 13 | # Provides method annotations: 14 | # 15 | # class Y 16 | # extend Anise::Annotator::Method 17 | # 18 | # def self.doc(string) 19 | # method_annotation(:doc=>string) 20 | # end 21 | # 22 | # doc "foo is cool" 23 | # def foo 24 | # # ... 25 | # end 26 | # end 27 | # 28 | # Provides annotated attributes: 29 | # 30 | # class Z 31 | # extend Anise::Annotator::Attribute 32 | # 33 | # attr :bar, Integer, :max=>10 34 | # end 35 | # 36 | module Anise 37 | require 'anise/version' 38 | require 'anise/core_ext' 39 | require 'anise/annotations' 40 | require 'anise/annotations/store' 41 | require 'anise/annotative' 42 | require 'anise/annotative/methods' 43 | require 'anise/annotative/attributes' 44 | require 'anise/annotative/variables' 45 | end 46 | -------------------------------------------------------------------------------- /demo/04_methods.md: -------------------------------------------------------------------------------- 1 | # Method Annotations 2 | 3 | Create a class that uses the `Annotative::Methods` mixin. 4 | 5 | class X 6 | extend Anise::Annotative::Methods 7 | 8 | def self.doc(string) 9 | method_annotation(:doc=>string.to_s) 10 | end 11 | 12 | doc "See what I mean?" 13 | 14 | def see 15 | puts "Yes, I see!" 16 | end 17 | end 18 | 19 | See that it is set. 20 | 21 | X.ann(:see, :doc) #=> "See what I mean?" 22 | 23 | Method Annotators can override the standard annotation procedure 24 | with a custom procedure. In such case no annotations will actually 25 | be created unless the `#ann` is called in the procedure. 26 | 27 | class Y 28 | extend Anise::Annotative::Methods 29 | 30 | def self.list 31 | @list ||= [] 32 | end 33 | 34 | def self.doc(string) 35 | method_annotation do |method| 36 | list << [method, string] 37 | end 38 | end 39 | 40 | doc "See here!" 41 | 42 | def see 43 | puts "Yes, I see!" 44 | end 45 | end 46 | 47 | See that it is set. 48 | 49 | Y.list #=> [[:see, "See here!"]] 50 | 51 | -------------------------------------------------------------------------------- /lib/anise/core_ext.rb: -------------------------------------------------------------------------------- 1 | #require 'facets/inheritor' # removed dependency 2 | 3 | class Module 4 | # 5 | # Module extension to return attribute methods. These are all methods 6 | # that start with `attr_`. This method can be overriden in special cases 7 | # to work with attribute annotations. 8 | # 9 | def attribute_methods 10 | list = [] 11 | public_methods(true).each do |m| 12 | list << m if m.to_s =~ /^attr_/ 13 | end 14 | protected_methods(true).each do |m| 15 | list << m if m.to_s =~ /^attr_/ 16 | end 17 | private_methods(true).each do |m| 18 | list << m if m.to_s =~ /^attr_/ 19 | end 20 | return list 21 | end 22 | end 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 | 50 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | (BSD-2-Clause License) 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY Thomas Sawyer ``AS IS'' AND ANY EXPRESS 14 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 16 | NO EVENT SHALL Thomas Sawyer OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 17 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 18 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 20 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 22 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | gem: 3 | active : true 4 | 5 | dnote: 6 | labels : ~ 7 | output : log/notes.html 8 | 9 | qedoc: 10 | files: demo 11 | title: "Anise Demonstrandum" 12 | output: 13 | - DEMO.md 14 | - web/demo.html 15 | 16 | qed: 17 | active: true 18 | 19 | #rubytest: 20 | # active: true 21 | 22 | github: 23 | gh_pages: web 24 | 25 | vclog: 26 | active: false 27 | output: 28 | ~ log/changes.html 29 | - lob/history.html 30 | 31 | email: 32 | service : Email 33 | file : ~ 34 | subject : ~ 35 | mailto : ruby-talk@ruby-lang.org 36 | 37 | # from : transfire@gmail.com 38 | # server : <%= ENV['EMAIL_SERVER'] %> 39 | # port : <%= ENV['EMAIL_PORT'] %> 40 | # account : <%= ENV['EMAIL_ACCOUNT'] %> 41 | # domain : <%= ENV['EMAIL_DOMAIN'] %> 42 | # login : <%= ENV['EMAIL_LOGIN'] %> 43 | # secure : <%= ENV['EMAIL_SECURE'] %> 44 | 45 | # from : admin@tigerops.org 46 | # server : mail.tigerops.org 47 | # port : 25 48 | # domain : tigerops.org 49 | # account : admin@tigerops.org 50 | # secure : false 51 | # login : login 52 | 53 | #rubyforge: 54 | # service : Rubyforge 55 | # unixname : anise 56 | # groupid : 7160 57 | # active : false 58 | 59 | #rdoc: 60 | # service : rdoc 61 | # format : newfish #hanna 62 | # exclude : [VERSION] 63 | # output : site/doc 64 | 65 | -------------------------------------------------------------------------------- /work/test/toplevel/test_toplevel_attribute.rb: -------------------------------------------------------------------------------- 1 | #require 'anise/annotation' 2 | require 'anise/attribute' 3 | 4 | include Anise::Attribute 5 | 6 | class Test_Attribute_Toplevel < Test::Unit::TestCase 7 | class X 8 | attr :q 9 | attr :a, :x => 1 10 | end 11 | 12 | def test_attr_a 13 | assert_equal( {:x=>1}, X.ann(:a) ) 14 | end 15 | end 16 | 17 | class Test_Attribute_Toplevel_Using_Attr < Test::Unit::TestCase 18 | class A 19 | attr :x, :cast=>"to_s" 20 | end 21 | 22 | def test_01 23 | assert_equal( "to_s", A.ann(:x,:cast) ) 24 | end 25 | end 26 | 27 | class Test_Attribute_Toplevel_Using_Attr_Accessor < Test::Unit::TestCase 28 | class A 29 | attr_accessor :x, :cast=>"to_s" 30 | end 31 | 32 | def test_01 33 | a = A.new 34 | assert_equal( [:x], A.instance_attributes - [:taguri] ) # taguri is from YAML :( 35 | end 36 | end 37 | 38 | 39 | =begin 40 | require 'annotable/attributes' 41 | require 'test/unit' 42 | 43 | include Anise::Attributes 44 | 45 | $self = self 46 | 47 | attr :b, :y => 1 48 | 49 | class Test_Attribute_Toplevel < Test::Unit::TestCase 50 | 51 | attr :q 52 | 53 | attr :a, :x => 1 54 | 55 | def test_attr_a 56 | assert_equal( {:x=>1}, self.class.ann(:a) ) 57 | end 58 | 59 | def test_attr_b 60 | assert_equal( {:y=>1}, $self.class.ann(:b) ) 61 | end 62 | 63 | end 64 | =end 65 | 66 | -------------------------------------------------------------------------------- /work/NOTES.rdoc: -------------------------------------------------------------------------------- 1 | = DEVELOPERS NOTES 2 | 3 | == Easier Annotation Callbacks 4 | 5 | An idea for making callbacks easier and more versitle. 6 | 7 | trace_annotation :key => :default do |base, ref, key, value| 8 | base.module_eval do 9 | define_method(key) do 10 | instance_variable_set(key, value) unless instance_variable_defined(key) 11 | base.module_eval{ attr key } 12 | instance_variable_get(key) 13 | end 14 | } 15 | end 16 | 17 | The method #trace_annotation could take a single key argument, or a hash specifying which of the 18 | four variables to match per above example, and/or perhaps an array in which a Hole could be used. Eg. 19 | 20 | trace_annotation [__, __, :default, __] do |base, ref, key, value| 21 | base.module_eval do 22 | define_method(key) do 23 | instance_variable_set(key, value) unless instance_variable_defined(key) 24 | base.module_eval{ attr key } 25 | instance_variable_get(key) 26 | end 27 | } 28 | end 29 | 30 | Technically all of this could be done with the current #annotation_added callback, but it would 31 | make it much easier to add callbacks without stepping on other peoples toes. As it stands we 32 | must play the "alias_method, call the alias and don't forget to call super game". 33 | 34 | 35 | == Reference 36 | 37 | Makoto Kuwata also put together an interesting Annotations project more recently. 38 | 39 | * https://github.com/kwatch/annotation/blob/master/lib/annotation.rb 40 | 41 | Basically his annotations are Anise's annotators. But his approach is appealling 42 | and eventually it would be nice if annotators could be inherited via mixins too. 43 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - var 6 | authors: 7 | - name: trans 8 | email: transfire@gmail.com 9 | organizations: 10 | - name: Rubyworks 11 | requirements: 12 | - groups: 13 | - build 14 | development: true 15 | name: detroit 16 | - groups: 17 | - build 18 | development: true 19 | name: ergo 20 | - groups: 21 | - test 22 | development: true 23 | name: qed 24 | - groups: 25 | - test 26 | development: true 27 | name: ae 28 | - groups: 29 | - test 30 | development: true 31 | name: citron 32 | - groups: 33 | - test 34 | development: true 35 | name: rubytest 36 | - groups: 37 | - test 38 | development: true 39 | name: rubytest-cli 40 | conflicts: [] 41 | alternatives: [] 42 | resources: 43 | - type: home 44 | uri: http://rubyworks.github.com/anise 45 | label: Homepage 46 | - type: code 47 | uri: http://github.com/rubyworks/anise 48 | label: Source Code 49 | - type: bugs 50 | uri: http://github.com/rubyworks/anise/issues 51 | label: Issue Tracker 52 | - type: chat 53 | uri: http://chat.us.freenode.net/rubyworks 54 | label: IRC Channel 55 | - type: mail 56 | uri: http://groups.google.com/groups/rubyworks-mailinglist 57 | label: Mailing List 58 | repositories: 59 | - name: upstream 60 | scm: git 61 | uri: http://github.com/rubyworks/anise.git 62 | categories: [] 63 | copyrights: 64 | - holder: Rubyworks 65 | year: '2008' 66 | license: BSD-2-Clause 67 | customs: [] 68 | paths: 69 | lib: 70 | - lib 71 | created: '2008-02-21' 72 | summary: Dynamic Annotations for Ruby 73 | title: Anise 74 | version: 0.7.1 75 | name: anise 76 | description: Anise is an annotations systems for the Ruby programming lanaguage. 77 | date: '2013-03-09' 78 | -------------------------------------------------------------------------------- /work/consider/annotations.rb: -------------------------------------------------------------------------------- 1 | # Ultimately it might be nice to replace the general hashes used to 2 | # store annotations with something more sophisticated. The main benefit 3 | # being that we could control in-place operations, so that the need for 4 | # a normal and a bang method wiould not be needed (e.g. #ann and #ann!). 5 | 6 | module Anise 7 | 8 | class Namespaces 9 | # 10 | def initialize(base) 11 | @base = base 12 | @table = {} 13 | end 14 | 15 | # 16 | def [](ns) 17 | @table[ns] || ancestor[ns] 18 | end 19 | 20 | # 21 | def to_h 22 | ann = {} 23 | ancestors.reverse_each do |anc| 24 | next unless anc < Annotator 25 | if h = anc.annotations[ns][ref] 26 | ann.merge!(h) 27 | end 28 | end 29 | end 30 | 31 | # 32 | def ancestor 33 | @base.ancestors[1].namespaces 34 | end 35 | 36 | end 37 | 38 | class References 39 | # 40 | def initialize(namespace) 41 | @namespace = namespace 42 | @table = {} 43 | end 44 | 45 | # 46 | def [](ref) 47 | @table[ref] || ancestor[ref] 48 | end 49 | end 50 | 51 | class Annotations 52 | # 53 | def initialize(parent) 54 | @parent = parent # Reference 55 | @table = {} 56 | end 57 | 58 | # 59 | def [](key) 60 | @table[key] || ancestor[key] 61 | end 62 | 63 | # 64 | def update(hash) 65 | hash.each do |k,v| 66 | @table[k] = v 67 | end 68 | end 69 | 70 | # 71 | def to_h 72 | ancestor.to_h.merge(@table) 73 | end 74 | 75 | private 76 | 77 | def ancestor 78 | 79 | end 80 | 81 | end 82 | 83 | end 84 | -------------------------------------------------------------------------------- /demo/01_annotations.md: -------------------------------------------------------------------------------- 1 | # Annotations 2 | 3 | ## Creating and Reading Annotations 4 | 5 | Load the Anise library. 6 | 7 | require 'anise' 8 | 9 | Given an example class X we can apply annotations to it using the #ann method. 10 | 11 | class X 12 | extend Anise::Annotations 13 | 14 | ann :x1, :a=>1 15 | ann :x1, :b=>2 16 | end 17 | 18 | We can then use #ann to lookup the set annotations. 19 | 20 | X.ann(:x1,:a).should == 1 21 | 22 | The #ann method is a public interface, so we can define annotation externally as well. 23 | 24 | X.ann :x1, :a => 2 25 | X.ann(:x1, :a).should == 2 26 | 27 | ## Annotation Added Callback 28 | 29 | Given a sample class Y, we can use a standard callback method #annotation_added(). 30 | 31 | class Y 32 | extend Anise::Annotations 33 | 34 | class << self 35 | attr :last_callback 36 | 37 | def annotation_added(ref, ns) 38 | @last_callback = [ns, ref, ann(ref/ns)] 39 | end 40 | end 41 | end 42 | 43 | Now if we add an annotation, we will see the callback catches it. 44 | 45 | Y.ann :x1, :a=>1 46 | Y.last_callback.should == [:ann, :x1, {:a => 1}] 47 | 48 | We will do it again to be sure. 49 | 50 | Y.ann :x1, :b=>2 51 | Y.last_callback.should == [:ann, :x1, {:a => 1, :b => 2}] 52 | 53 | ## Using Callbacks for Attribute Defaults 54 | 55 | class ::Module 56 | def annotation_added(key, ns) 57 | return unless ns == :ann 58 | base = self 59 | if value = ann(key, :default) 60 | define_method(key) do 61 | instance_variable_set("@#{key}", value) unless instance_variable_defined?("@#{key}") 62 | base.module_eval{ attr key } 63 | instance_variable_get("@#{key}") 64 | end 65 | end 66 | end 67 | end 68 | 69 | Try it out. 70 | 71 | class Z 72 | extend Anise::Annotations 73 | 74 | attr :a 75 | ann :a, :default => 10 76 | end 77 | 78 | z = Z.new 79 | z.a.should == 10 80 | z.a.should == 10 81 | 82 | -------------------------------------------------------------------------------- /work/INDEX.md: -------------------------------------------------------------------------------- 1 | # METADATA INDEX 2 | 3 | ### TITLE 4 | 5 |

Anise

6 | 7 | ### VERSION 8 | 9 |

0.7.0

10 | 11 | ### SUMMARY 12 | 13 |

Dynamic Annotation System

14 | 15 | ### DESCRIPTION 16 | 17 |

Anise is an annotations systems for the Ruby programming lanaguage.

18 | 19 | ### REQUIREMENTS 20 | 21 | 27 | 28 | ### RESOURCES 29 | 30 | 39 | 40 | ### AUTHORS 41 | 42 | 47 | 48 | ### ORGANIZATIONS 49 | 50 | 53 | 54 | ### COPYRIGHTS 55 | 56 | 62 | 63 | ### CREATED 64 | 65 |

2008-02-21

66 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # RELEASE HISTORY 2 | 3 | ## 0.7.0 / 2012-03-26 4 | 5 | This release has some major API changes. Most significantly a number 6 | of modules have been renamed. The `Method` module has been renamed to 7 | `Annotative::Methods`. Likewise the `Attribute` module has been ranamed 8 | to `Annotative::Attributes`, and so on. These have been renamed so that 9 | including `Anise` in the toplevel will not cause conflicts with any 10 | other modules or classes an application or library might be using. 11 | In addition these modules now must use `extend` rather then `include` 12 | to be mixed into a class or module, since they conatin only class methods. 13 | 14 | Changes: 15 | 16 | * Rename Annotations module to Annotations::Store. 17 | * Rename Annotation module to Annotations. 18 | * Rename Method module to Annotative::Methods. 19 | * Rename Attribute module to Annotative::Attributes. 20 | * Add #method_annotation for use in custom class method. 21 | * Discourage #method_annotator in favor of #method_annotation. 22 | 23 | 24 | ## 0.6.0 / 2011-05-16 25 | 26 | This release fixes an bug in which append_features cant be called b/c 27 | it is a private method. This release also renames `ClassMethods` 28 | modules to `Aid`. 29 | 30 | Changes: 31 | 32 | * Fixed private method call to #append_features. 33 | * Rename ClassMethods to Aid. 34 | 35 | 36 | ## 0.5.0 / 2011-04-30 37 | 38 | The primary changes in this release are behind the scenes implementation 39 | improvements. The most significant of which is the simplification of 40 | the #append_features code. In addition, annotators have been enhanced 41 | to assign a single argument if one, and an array of arguments if there 42 | are more than one. They can also override the callback altogether. 43 | 44 | Changes: 45 | 46 | * Simplified #append_features code. 47 | * Use ClassMethod submodules. 48 | * Annotators differentiate one vs. multiple arguments. 49 | * Annotators can override method_added callback. 50 | 51 | 52 | ## 0.4.0 / 2009-05-28 53 | 54 | This version adds a callback method called #annotation_added 55 | --a striaght-forward callback method patterned after Ruby's 56 | other built-in callback methods. The callback should be enough 57 | to allow for the creation of "active" annotations. 58 | 59 | Changes: 60 | 61 | * Added annotation_added callback. 62 | 63 | 64 | ## 0.2.1 / 2008-10-31 65 | 66 | Project reorganization release --mostly some file names have changed. 67 | 68 | Changes: 69 | 70 | * Renamed some lib files. 71 | 72 | 73 | ## 0.2.0 / 2008-10-28 74 | 75 | By making Annotations a module, it can not be used in only the clases 76 | it is needed. 77 | 78 | Changes: 79 | 80 | * Annotations is a module rather than a core extenstion to Module. 81 | 82 | 83 | ## 0.1.1 / 2008-10-17 84 | 85 | Ahoy, mate! This is the first release of Anise. 86 | 87 | Changes: 88 | 89 | * initial release 90 | 91 | -------------------------------------------------------------------------------- /work/consider/attrait.rb: -------------------------------------------------------------------------------- 1 | 2 | # class C 3 | # attr :x, :default=>10 4 | # 5 | # attr :y do 6 | # inherit true 7 | # default 20 8 | # write { |x| x.to_s } 9 | # end 10 | # end 11 | # 12 | # a = C.new 13 | # a.x #=> 10 14 | # a.y #=> "20" 15 | # 16 | class Attribute 17 | 18 | # 19 | def initialize(base, name, options={}, &block) 20 | @base = base 21 | @name = name 22 | 23 | options.each do |k,v| 24 | send("#{k}=", v) 25 | end 26 | 27 | DSL.new(self).instance_eval(&block) if block 28 | end 29 | 30 | # 31 | def base 32 | @base 33 | end 34 | 35 | # 36 | def name 37 | @name 38 | end 39 | 40 | # 41 | attr_accessor :default 42 | 43 | # 44 | attr_accessor :inherit 45 | 46 | # 47 | def write 48 | @write 49 | end 50 | 51 | # 52 | def write=(procedure) 53 | @write = procedure.to_proc 54 | end 55 | 56 | # 57 | def build 58 | build_initializer 59 | build_reader 60 | build_writer 61 | build_query 62 | end 63 | 64 | # 65 | def build_initializer 66 | name = name() 67 | default = default() 68 | if inherit 69 | base.module_eval do 70 | define_method("#{ name }!") do 71 | value = self.class.__send__(name) rescue default 72 | __send__("#{ name }=", value) 73 | end 74 | end 75 | else 76 | base.module_eval do 77 | define_method("#{ name }!") do 78 | #p default 79 | __send__("#{ name }=", default) 80 | end 81 | end 82 | end 83 | end 84 | 85 | # 86 | def build_reader 87 | name = name() 88 | base.module_eval %{ 89 | def #{name} 90 | #{name}! unless instance_variable_defined?("@#{name}") 91 | @#{name} 92 | end 93 | } 94 | end 95 | 96 | # 97 | def build_writer 98 | name = name() 99 | write = write() 100 | if write 101 | base.module_eval do 102 | define_method("#{name}=", &write) 103 | end 104 | else 105 | base.module_eval %{ 106 | def #{name}=x 107 | @#{name}=x 108 | end 109 | } 110 | end 111 | end 112 | 113 | # 114 | def build_query 115 | name = name() 116 | base.module_eval %{ 117 | def #{name}? 118 | #{name} 119 | end 120 | } 121 | end 122 | 123 | # 124 | class DSL 125 | 126 | def initialize(attribute) 127 | @attribute = attribute 128 | end 129 | 130 | def default(value) 131 | @attribute.default = value 132 | end 133 | 134 | def inherit(flag) 135 | @attribute.inherit = flag 136 | end 137 | 138 | def write(&block) 139 | @attribute.write = block 140 | end 141 | 142 | end 143 | 144 | end 145 | 146 | 147 | class Module 148 | def attr(name, ann={}, &block) 149 | Attribute.new(self, name, ann, &block).build 150 | end 151 | end 152 | 153 | -------------------------------------------------------------------------------- /lib/anise/annotative/variables.rb: -------------------------------------------------------------------------------- 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) 100 | -------------------------------------------------------------------------------- /work/test/toplevel/test_toplevel_annotation.rb: -------------------------------------------------------------------------------- 1 | require 'anise/annotator' 2 | 3 | annotator :ann 4 | 5 | class Test_Annotator_Toplevel_0 < Test::Unit::TestCase 6 | 7 | class X 8 | attr :a 9 | ann :a, :class => Integer 10 | ann :@a, :valid => lambda{ |x| x.is_a?(Integer) } 11 | 12 | def initialize(a) 13 | @a = a 14 | end 15 | 16 | def validate 17 | instance_variables.each do |iv| 18 | if validator = self.class.ann(iv)[:valid] 19 | value = instance_variable_get(iv) 20 | unless validator.call(value) 21 | raise "Invalid value #{value} for #{iv}" 22 | end 23 | end 24 | end 25 | end 26 | end 27 | 28 | def test_annotation_class 29 | assert_equal(Integer, X.ann(:a, :class)) 30 | end 31 | 32 | def test_annotation_validate 33 | x = X.new(1) 34 | assert_nothing_raised{ x.validate } 35 | end 36 | 37 | end 38 | 39 | class Test_Annotator_Toplevel_1 < Test::Unit::TestCase 40 | class X 41 | def x1 ; end 42 | ann :x1, :a=>1 43 | ann :x1, :b=>2 44 | end 45 | 46 | def test_1_01 47 | assert_equal( X.ann(:x1,:a), X.ann(:x1,:a) ) 48 | assert_equal( X.ann(:x1,:a).object_id, X.ann(:x1,:a).object_id ) 49 | end 50 | def test_1_02 51 | X.ann :x1, :a => 2 52 | assert_equal( 2, X.ann(:x1,:a) ) 53 | end 54 | end 55 | 56 | class Test_Annotator_Toplevel_2 < Test::Unit::TestCase 57 | class X 58 | def x1 ; end 59 | ann :x1, :a=>1 60 | ann :x1, :b=>2 61 | end 62 | class Y < X ; end 63 | 64 | def test_2_01 65 | assert_equal( Y.ann(:x1,:a), Y.ann(:x1,:a) ) 66 | assert_equal( Y.ann(:x1,:a).object_id, Y.ann(:x1,:a).object_id ) 67 | end 68 | def test_2_02 69 | assert_equal( 1, Y.ann(:x1,:a) ) 70 | assert_equal( 2, Y.ann(:x1,:b) ) 71 | end 72 | def test_2_03 73 | Y.ann :x1,:a => 2 74 | assert_equal( 2, Y.ann(:x1,:a) ) 75 | assert_equal( 2, Y.ann(:x1,:b) ) 76 | end 77 | end 78 | 79 | class Test_Annotator_Toplevel_3 < Test::Unit::TestCase 80 | class X 81 | ann :foo, Integer 82 | end 83 | class Y < X 84 | ann :foo, String 85 | end 86 | 87 | def test_3_01 88 | assert_equal( Integer, X.ann(:foo, :class) ) 89 | end 90 | def test_3_02 91 | assert_equal( String, Y.ann(:foo, :class) ) 92 | end 93 | end 94 | 95 | class Test_Annotator_Toplevel_4 < Test::Unit::TestCase 96 | class X 97 | ann :foo, :doc => "hello" 98 | ann :foo, :bar => [] 99 | end 100 | class Y < X 101 | ann :foo, :class => String, :doc => "bye" 102 | end 103 | 104 | def test_4_01 105 | assert_equal( "hello", X.ann(:foo,:doc) ) 106 | end 107 | def test_4_02 108 | assert_equal( X.ann(:foo), { :doc => "hello", :bar => [] } ) 109 | end 110 | def test_4_03 111 | X.ann(:foo,:bar) << "1" 112 | assert_equal( ["1"], X.ann(:foo,:bar) ) 113 | end 114 | def test_4_04 115 | assert_equal( "bye", Y.ann(:foo,:doc) ) 116 | end 117 | def test_4_05 118 | #assert_equal( nil, Y.ann(:foo,:bar) ) 119 | assert_equal( ["1"], Y.ann(:foo,:bar) ) 120 | end 121 | def test_4_06 122 | Y.ann(:foo, :doc => "cap") 123 | assert_equal( "cap", Y.ann(:foo, :doc) ) 124 | end 125 | def test_4_07 126 | Y.ann!(:foo,:bar) << "2" 127 | assert_equal( ["1", "2"], Y.ann(:foo,:bar) ) 128 | assert_equal( ["1", "2"], Y.ann(:foo,:bar) ) 129 | assert_equal( ["1"], X.ann(:foo,:bar) ) 130 | end 131 | end 132 | 133 | -------------------------------------------------------------------------------- /work/deprecated/test_annotator_module.rb: -------------------------------------------------------------------------------- 1 | require 'anise/annotator' 2 | 3 | class Module 4 | annotator :ann 5 | end 6 | 7 | class Test_Annotator_Module_0 < Test::Unit::TestCase 8 | 9 | class X 10 | attr :a 11 | ann :a, :class => Integer 12 | ann :@a, :valid => lambda{ |x| x.is_a?(Integer) } 13 | 14 | def initialize(a) 15 | @a = a 16 | end 17 | 18 | def validate 19 | instance_variables.each do |iv| 20 | if validator = self.class.ann(iv)[:valid] 21 | value = instance_variable_get(iv) 22 | unless validator.call(value) 23 | raise "Invalid value #{value} for #{iv}" 24 | end 25 | end 26 | end 27 | end 28 | end 29 | 30 | def test_annotation_class 31 | assert_equal(Integer, X.ann(:a, :class)) 32 | end 33 | 34 | def test_annotation_validate 35 | x = X.new(1) 36 | assert_nothing_raised{ x.validate } 37 | end 38 | end 39 | 40 | class Test_Annotator_Module_1 < Test::Unit::TestCase 41 | class X 42 | def x1 ; end 43 | ann :x1, :a=>1 44 | ann :x1, :b=>2 45 | end 46 | 47 | def test_1_01 48 | assert_equal( X.ann(:x1,:a), X.ann(:x1,:a) ) 49 | assert_equal( X.ann(:x1,:a).object_id, X.ann(:x1,:a).object_id ) 50 | end 51 | def test_1_02 52 | X.ann :x1, :a => 2 53 | assert_equal( 2, X.ann(:x1,:a) ) 54 | end 55 | end 56 | 57 | class Test_Annotator_Module_2 < Test::Unit::TestCase 58 | class X 59 | def x1 ; end 60 | ann :x1, :a=>1 61 | ann :x1, :b=>2 62 | end 63 | class Y < X ; end 64 | 65 | def test_2_01 66 | assert_equal( Y.ann(:x1,:a), Y.ann(:x1,:a) ) 67 | assert_equal( Y.ann(:x1,:a).object_id, Y.ann(:x1,:a).object_id ) 68 | end 69 | def test_2_02 70 | assert_equal( 1, Y.ann(:x1,:a) ) 71 | assert_equal( 2, Y.ann(:x1,:b) ) 72 | end 73 | def test_2_03 74 | Y.ann :x1,:a => 2 75 | assert_equal( 2, Y.ann(:x1,:a) ) 76 | assert_equal( 2, Y.ann(:x1,:b) ) 77 | end 78 | end 79 | 80 | class Test_Annotator_Module_3 < Test::Unit::TestCase 81 | class X 82 | ann :foo, Integer 83 | end 84 | class Y < X 85 | ann :foo, String 86 | end 87 | 88 | def test_3_01 89 | assert_equal( Integer, X.ann(:foo, :class) ) 90 | end 91 | def test_3_02 92 | assert_equal( String, Y.ann(:foo, :class) ) 93 | end 94 | end 95 | 96 | class Test_Annotator_Module_4 < Test::Unit::TestCase 97 | class X 98 | ann :foo, :doc => "hello" 99 | ann :foo, :bar => [] 100 | end 101 | class Y < X 102 | ann :foo, :class => String, :doc => "bye" 103 | end 104 | 105 | def test_4_01 106 | assert_equal( "hello", X.ann(:foo,:doc) ) 107 | end 108 | def test_4_02 109 | assert_equal( X.ann(:foo), { :doc => "hello", :bar => [] } ) 110 | end 111 | def test_4_03 112 | X.ann(:foo,:bar) << "1" 113 | assert_equal( ["1"], X.ann(:foo,:bar) ) 114 | end 115 | def test_4_04 116 | assert_equal( "bye", Y.ann(:foo,:doc) ) 117 | end 118 | def test_4_05 119 | #assert_equal( nil, Y.ann(:foo,:bar) ) 120 | assert_equal( ["1"], Y.ann(:foo,:bar) ) 121 | end 122 | def test_4_06 123 | Y.ann(:foo, :doc => "cap") 124 | assert_equal( "cap", Y.ann(:foo, :doc) ) 125 | end 126 | def test_4_07 127 | Y.ann!(:foo,:bar) << "2" 128 | assert_equal( ["1", "2"], Y.ann(:foo,:bar) ) 129 | assert_equal( ["1", "2"], Y.ann(:foo,:bar) ) 130 | assert_equal( ["1"], X.ann(:foo,:bar) ) 131 | end 132 | end 133 | 134 | -------------------------------------------------------------------------------- /work/deprecated/annotations0.rb: -------------------------------------------------------------------------------- 1 | #require 'facets/hash/rekey' # FIXME: remove dependency 2 | #require 'facets/hash/op' # FIXME: remove dependency 3 | 4 | # = Annotations 5 | # 6 | # Annotations allows you to annontate objects, including methods with arbitrary 7 | # "metadata". These annotations don't do anything in themselves. They are 8 | # merely data. But you can put them to use. For instance an attribute 9 | # validator might check for an annotation called :valid and test against it. 10 | # 11 | # == Synopsis 12 | # 13 | # class X 14 | # attr :a 15 | # ann :@a, :valid => lambda{ |x| x.is_a?(Integer) } 16 | # 17 | # def validate 18 | # instance_variables.each do |iv| 19 | # if validator = self.class.ann(iv)[:valid] 20 | # value = instance_variable_get(iv) 21 | # unless validator.call(vale) 22 | # raise "Invalid value #{value} for #{iv}" 23 | # end 24 | # end 25 | # end 26 | # end 27 | # 28 | # end 29 | # 30 | #-- 31 | # By using a global veriable rather the definining a class instance variable 32 | # for each class/module, it is possible to quicky scan all annotations for the 33 | # entire system. To do the same without this would require scanning through 34 | # the ObjectSpace. (Still which is better?) 35 | # 36 | #$annotations = Hash.new { |h,k| h[k] = {} } 37 | #++ 38 | # 39 | # TODO: The ann(x).name notation is kind of nice. Would like to add that 40 | # back-in if reasonable. Basically this require heritage to be an 41 | # OpenObject rather than just a hash. 42 | # 43 | #-- 44 | # History Note 45 | # 46 | # * 2006-11-07 trans Created this ultra-concise version of annotations. 47 | #++ 48 | 49 | class ::Module 50 | 51 | def annotations 52 | #$annotations[self] 53 | @annotations ||= {} 54 | end 55 | 56 | def heritage(ref) 57 | ref = ref.to_sym 58 | ancestors.inject({}) { |memo, ancestor| 59 | ancestor.annotations[ref] ||= {} 60 | ancestor.annotations[ref] + memo #HERE 61 | } 62 | end 63 | 64 | # Set or read annotations. 65 | 66 | def ann( ref, keys_or_class=nil, keys=nil ) 67 | return heritage(ref) unless keys_or_class or keys 68 | 69 | if Class === keys_or_class 70 | keys ||= {} 71 | keys[:class] = keys_or_class 72 | else 73 | keys = keys_or_class 74 | end 75 | 76 | if Hash === keys 77 | ref = ref.to_sym 78 | keys = keys.inject({}){ |h,(k,v)| h[k.to_sym] = v; h} #rekey 79 | annotations[ref] ||= {} 80 | annotations[ref].update(keys) 81 | else 82 | key = keys.to_sym 83 | heritage(ref)[key] 84 | end 85 | end 86 | 87 | # To change an annotation's value in place for a given class or module 88 | # it first must be duplicated, otherwise the change may effect annotations 89 | # in the class or module's ancestors. 90 | 91 | def ann!( ref, keys_or_class=nil, keys=nil ) 92 | #return heritage(ref) unless keys_or_class or keys 93 | return annotations[ref] unless keys_or_class or keys 94 | 95 | if Class === keys_or_class 96 | keys ||= {} 97 | keys[:class] = keys_or_class 98 | else 99 | keys = keys_or_class 100 | end 101 | 102 | if Hash === keys 103 | ref = ref.to_sym 104 | keys = keys.inject({}){ |h,(k,v)| h[k.to_sym] = v; h} #rekey 105 | annotations[ref] ||= {} 106 | annotations[ref].update(keys) 107 | else 108 | key = keys.to_sym 109 | annotations[ref][key] = heritage(ref)[key].dup 110 | end 111 | end 112 | 113 | end 114 | 115 | # Copyright (c) 2005, 2008 TigerOps 116 | 117 | -------------------------------------------------------------------------------- /work/deprecated/annattr.rb: -------------------------------------------------------------------------------- 1 | # = annattr.rb 2 | # 3 | # == Copyright (c) 2005 Thomas Sawyer 4 | # 5 | # Ruby License 6 | # 7 | # This module is free software. You may use, modify, and/or redistribute this 8 | # software under the same terms as Ruby. 9 | # 10 | # This program is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | # FOR A PARTICULAR PURPOSE. 13 | # 14 | # == Authors & Contributors 15 | # 16 | # * Thomas Sawyer 17 | 18 | # Author:: Thomas Sawyer 19 | # Copyright:: Copyright (c) 2005 Thomas Sawyer 20 | # License:: Ruby License 21 | 22 | require 'facets/more/inheritor.rb' 23 | require 'facets/yore/annotation.rb' 24 | 25 | # = Annotated Attributes 26 | # 27 | # This framework modifies the attr_* methods to allow easy addition of annotations. 28 | # It the built in attribute methods (attr, attr_reader, attr_writer and attr_accessor), 29 | # to allow annotations to added to them directly rather than requireing a separate 30 | # #ann statement. 31 | # 32 | # class X 33 | # attr :a, :valid => lambda{ |x| x.is_a?(Integer) } 34 | # end 35 | # 36 | # See annotation.rb for more information. 37 | # 38 | # NOTE This library was designed to be backward compatible with the 39 | # standard versions of the same methods. 40 | 41 | class ::Module 42 | 43 | inheritor :attributes, [], :| 44 | 45 | def attr( *args ) 46 | args.flatten! 47 | case args.last 48 | when TrueClass 49 | args.pop 50 | attr_accessor( *args ) 51 | when FalseClass 52 | args.pop 53 | attr_reader( *args ) 54 | else 55 | attr_reader( *args ) 56 | end 57 | end 58 | 59 | code = '' 60 | [ :_reader, :_writer, :_accessor].each do |m| 61 | 62 | d = case m 63 | when :_reader 64 | %{def %1$s; @%1$s; end} 65 | when :_writer 66 | %{def %1$s=(x); @%1$s = x; end} 67 | when :_accessor 68 | %{def %1$s; @%1$s; end ; def %1$s=(x); @%1$s = x; end} 69 | else 70 | %{def %1$s; @%1$s; end} 71 | end 72 | 73 | code << %{ 74 | def attr#{m}(*args) 75 | args.flatten! 76 | 77 | harg = {} 78 | while args.last.is_a?(Hash) ; harg.update(args.pop) ; end 79 | 80 | harg[:class] = args.pop if args.last.is_a?(Class) 81 | 82 | raise if args.empty? and harg.empty? 83 | 84 | if args.empty? 85 | harg.each { |a,h| 86 | module_eval( "#{d}" % a) 87 | a = a.to_sym 88 | ann(a,h) 89 | } 90 | attributes!.concat( harg.keys ) #merge! 91 | # return the names of the attributes created 92 | return harg.keys 93 | else 94 | args.each { |a| 95 | module_eval( "#{d}" % a) 96 | a = a.to_sym 97 | ann(a,harg) 98 | } 99 | attributes!.concat( args ) #merge! 100 | # return the names of the attributes created 101 | return args 102 | end 103 | end 104 | } 105 | 106 | end 107 | 108 | class_eval( code ) 109 | 110 | alias_method :attribute, :attr_accessor 111 | 112 | end 113 | 114 | 115 | 116 | # _____ _ 117 | # |_ _|__ ___| |_ 118 | # | |/ _ \/ __| __| 119 | # | | __/\__ \ |_ 120 | # |_|\___||___/\__| 121 | # 122 | 123 | =begin testing 124 | 125 | require 'test/unit' 126 | 127 | class TC01 < Test::Unit::TestCase 128 | class A 129 | attr_accessor :x, :cast=>"to_s" 130 | end 131 | 132 | def test_09_001 133 | a = A.new 134 | assert_equal( [:x], A.attributes ) 135 | end 136 | end 137 | 138 | class TC10 < Test::Unit::TestCase 139 | class A 140 | attr :x, :cast=>"to_s" 141 | end 142 | 143 | def test_10_001 144 | assert_equal( "to_s", A.ann.x.cast ) 145 | end 146 | end 147 | 148 | =end 149 | -------------------------------------------------------------------------------- /test/case_annotations.rb: -------------------------------------------------------------------------------- 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 174 | -------------------------------------------------------------------------------- /lib/anise/annotative/methods.rb: -------------------------------------------------------------------------------- 1 | module Anise 2 | 3 | module Annotative 4 | 5 | # TODO: Ensure thread-safety of @_pending_annotations 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) 132 | -------------------------------------------------------------------------------- /lib/anise/annotations.rb: -------------------------------------------------------------------------------- 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) 133 | -------------------------------------------------------------------------------- /lib/anise/annotations/store.rb: -------------------------------------------------------------------------------- 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 137 | -------------------------------------------------------------------------------- /work/test/toplevel/test_anise_toplevel.rb: -------------------------------------------------------------------------------- 1 | require 'anise' 2 | 3 | include Anise 4 | 5 | class Test_Anise_Toplevel_Annotation_0 < Test::Unit::TestCase 6 | class X 7 | attr :a 8 | 9 | ann :@a, :valid => lambda{ |x| x.is_a?(Integer) } 10 | 11 | ann :a, :class => Integer 12 | 13 | def initialize(a) 14 | @a = a 15 | end 16 | 17 | def validate 18 | instance_variables.each do |iv| 19 | if validator = self.class.ann(iv)[:valid] 20 | value = instance_variable_get(iv) 21 | unless validator.call(value) 22 | raise "Invalid value #{value} for #{iv}" 23 | end 24 | end 25 | end 26 | end 27 | end 28 | 29 | def test_annotation_class 30 | assert_equal(Integer, X.ann(:a, :class)) 31 | end 32 | 33 | def test_annotation_validate 34 | x = X.new(1) 35 | assert_nothing_raised{ x.validate } 36 | end 37 | end 38 | 39 | class Test_Anise_Toplevel_Annotation_1 < Test::Unit::TestCase 40 | class X 41 | def x1 ; end 42 | ann :x1, :a=>1 43 | ann :x1, :b=>2 44 | end 45 | 46 | def test_1_01 47 | assert_equal( X.ann(:x1,:a), X.ann(:x1,:a) ) 48 | assert_equal( X.ann(:x1,:a).object_id, X.ann(:x1,:a).object_id ) 49 | end 50 | def test_1_02 51 | X.ann :x1, :a => 2 52 | assert_equal( 2, X.ann(:x1,:a) ) 53 | end 54 | end 55 | 56 | class Test_Anise_Toplevel_Annotation_2 < Test::Unit::TestCase 57 | class X 58 | def x1 ; end 59 | ann :x1, :a=>1 60 | ann :x1, :b=>2 61 | end 62 | class Y < X ; end 63 | 64 | def test_2_01 65 | assert_equal( Y.ann(:x1,:a), Y.ann(:x1,:a) ) 66 | assert_equal( Y.ann(:x1,:a).object_id, Y.ann(:x1,:a).object_id ) 67 | end 68 | def test_2_02 69 | assert_equal( 1, Y.ann(:x1,:a) ) 70 | assert_equal( 2, Y.ann(:x1,:b) ) 71 | end 72 | def test_2_03 73 | Y.ann :x1,:a => 2 74 | assert_equal( 2, Y.ann(:x1,:a) ) 75 | assert_equal( 2, Y.ann(:x1,:b) ) 76 | end 77 | end 78 | 79 | class Test_Anise_Toplevel_Annotation_3 < Test::Unit::TestCase 80 | class X 81 | ann :foo, Integer 82 | end 83 | class Y < X 84 | ann :foo, String 85 | end 86 | 87 | def test_3_01 88 | assert_equal( Integer, X.ann(:foo, :class) ) 89 | end 90 | def test_3_02 91 | assert_equal( String, Y.ann(:foo, :class) ) 92 | end 93 | end 94 | 95 | class Test_Anise_Toplevel_Annotation_4 < Test::Unit::TestCase 96 | class X 97 | ann :foo, :doc => "hello" 98 | ann :foo, :bar => [] 99 | end 100 | class Y < X 101 | ann :foo, :class => String, :doc => "bye" 102 | end 103 | 104 | def test_4_01 105 | assert_equal( "hello", X.ann(:foo,:doc) ) 106 | end 107 | def test_4_02 108 | assert_equal( X.ann(:foo), { :doc => "hello", :bar => [] } ) 109 | end 110 | def test_4_03 111 | X.ann(:foo,:bar) << "1" 112 | assert_equal( ["1"], X.ann(:foo,:bar) ) 113 | end 114 | def test_4_04 115 | assert_equal( "bye", Y.ann(:foo,:doc) ) 116 | end 117 | def test_4_05 118 | #assert_equal( nil, Y.ann(:foo,:bar) ) 119 | assert_equal( ["1"], Y.ann(:foo,:bar) ) 120 | end 121 | def test_4_06 122 | Y.ann(:foo, :doc => "cap") 123 | assert_equal( "cap", Y.ann(:foo, :doc) ) 124 | end 125 | def test_4_07 126 | Y.ann!(:foo,:bar) << "2" 127 | assert_equal( ["1", "2"], Y.ann(:foo,:bar) ) 128 | assert_equal( ["1", "2"], Y.ann(:foo,:bar) ) 129 | assert_equal( ["1"], X.ann(:foo,:bar) ) 130 | end 131 | end 132 | 133 | class Test_Anise_Toplevel_Annotator < Test::Unit::TestCase 134 | class X 135 | annotator :req 136 | 137 | req 'r' 138 | def a ; "a"; end 139 | 140 | req 's', 't' 141 | attr :b 142 | 143 | def initialize 144 | @b = "b" 145 | end 146 | end 147 | 148 | def test_normal 149 | x = X.new 150 | assert_equal("a", x.a) 151 | assert_equal("b", x.b) 152 | end 153 | 154 | def test_annotated_a 155 | assert_equal( {:req=>'r'}, X.ann(:a) ) 156 | end 157 | 158 | def test_annotated_b 159 | assert_equal( {:req=>['s', 't']}, X.ann(:b) ) 160 | end 161 | end 162 | 163 | class Test_Anise_Toplevel_Attribute < Test::Unit::TestCase 164 | class X 165 | attr :q 166 | attr :a, :x => 1 167 | end 168 | 169 | def test_attr_a 170 | assert_equal( {:x=>1}, X.ann(:a) ) 171 | end 172 | end 173 | 174 | class Test_Anise_Toplevel_Attribute_Using_Attr < Test::Unit::TestCase 175 | class A 176 | attr :x, :cast=>"to_s" 177 | end 178 | 179 | def test_01 180 | assert_equal( "to_s", A.ann(:x,:cast) ) 181 | end 182 | end 183 | 184 | class Test_Anise_Toplevel_Attribute_Using_Attr_Accessor < Test::Unit::TestCase 185 | class A 186 | attr_accessor :x, :cast=>"to_s" 187 | end 188 | 189 | def test_instance_attributes 190 | a = A.new 191 | assert_equal( [:x], A.instance_attributes - [:taguri] ) 192 | end 193 | end 194 | 195 | -------------------------------------------------------------------------------- /lib/anise/annotative/attributes.rb: -------------------------------------------------------------------------------- 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) 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Anise 2 | 3 | [Homepage](http://rubyworks.github.com/anise) / 4 | [Documentation](http://rubydoc.info/gems/anise) / 5 | [Report Issue](http://github.com/rubyworks/anise/issues) / 6 | [Source Code](http://github.com/rubyworks/anise) 7 | 8 | [![Gem Version](https://badge.fury.io/rb/anise.png)](http://badge.fury.io/rb/anise) 9 | [![Build Status](https://secure.travis-ci.org/rubyworks/anise.png)](http://travis-ci.org/rubyworks/anise)     10 | [![Flattr Me](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/324911/Rubyworks-Ruby-Development-Fund) 11 | 12 | 13 | ## *Dynamic Annotations for Ruby* 14 | 15 | Anise is an Annotation System for the Ruby programming language. 16 | Unlike most other annotations systems it is not a comment-based or 17 | macro-based system that sits over-and-above the rest of the code. 18 | Rather, Anise is a dynamic annotations system operating at runtime. 19 | 20 | The following example briefly demonstrates all three major features. To use 21 | any of them first require the `anise` library. 22 | 23 | require 'anise' 24 | 25 | General annotations are provided by the `Anise::Annotations` module. 26 | 27 | class X 28 | extend Anise::Annotations 29 | 30 | ann :grape, :class=>String 31 | end 32 | 33 | X.ann(:grape, :class) #=> String 34 | 35 | Annotated attributes can be easily added to a class via the `Annotative::Attributes` 36 | module. 37 | 38 | class X 39 | extend Anise::Annotative::Attributes 40 | 41 | attr :baz, Integer, :max => 10 42 | end 43 | 44 | X.ann(:baz) #=> {:class=>Integer, :max=>10} 45 | 46 | Mewthod annotations can be had via the `Annotative::Methods` module. 47 | 48 | class X 49 | extend Anise::Annotative::Methods 50 | 51 | def self.doc(string) 52 | method_annotation(:doc=>string) 53 | end 54 | 55 | doc "This is an entry." 56 | 57 | def bar 58 | # ... 59 | end 60 | end 61 | 62 | X.ann(:bar) #=> {:doc=>"This is an entry."} 63 | 64 | Any of these modules can be used in conjunction. Since both `Annotative::Methods` 65 | and `Annotative::Attributes` preclude `Annotations` all three can be used by simply 66 | using the later two. 67 | 68 | class X 69 | extend Anise::Annotative::Attributes 70 | extend Anise::Annotative::Methods 71 | 72 | ... 73 | end 74 | 75 | Note also that the `Anise` module is clean and contains only modules and classes 76 | with detailed names starting the "Annotat-", so it is prefectly convenient for 77 | inclusion in the toplevel namespace or your own applications namespace. 78 | 79 | module MyApp 80 | include Anise 81 | 82 | class Foo 83 | extend Annotative::Attributes 84 | 85 | ... 86 | end 87 | end 88 | 89 | 90 | ## Installation 91 | 92 | ### RubyGems 93 | 94 | RubyGems.org hosts the [gem](http://rubygems.org/gems/anise) package. 95 | To install via RubyGems simply open a console and type: 96 | 97 | gem install anise 98 | 99 | ### Setup.rb (not recommended) 100 | 101 | To manually install you will need Setup.rb (see http://setup.rubyforge.org). 102 | Then download the tarball package and do: 103 | 104 | $ tar -xvzf anise-0.2.0.tgz 105 | $ cd anise-0.2.0 106 | $ sudo setup.rb all 107 | 108 | 109 | ## Documentation 110 | 111 | ### Demonstrations 112 | 113 | Fully tested demonstrations can be seen in the DEMO document. 114 | 115 | ### API Reference 116 | 117 | The [API documentation](http://rubydoc.info/gems/anise/frames) is available 118 | in YARD format via [rubydoc.info](a href="http://rubydoc.info"). 119 | 120 | 121 | ## Support 122 | 123 | If you experience a problem, have a question or a feature request file a ticket 124 | with the [issue tracker](http://github.com/rubyworks/anise/issues) on GitHub. 125 | 126 | If you would like to discuss something about this project in more detail try 127 | contacting the author(s) via the Rubyworks [IRC channel](http://chat.us.freenode.net/rubyworks) 128 | or the [Mailing List](http://groups.google.com/groups/rubyworks-mailinglist). 129 | 130 | 131 | ## Development 132 | 133 | ### Contributing 134 | 135 | If you would like to contribute code or documentation to the Anise project, fork 136 | the upstream repository and create a branch for you changes. When your changes are 137 | ready for review (and no, they do not have to 100% perfect if you still have some 138 | issues you need help working out) then submit a pull request. 139 | 140 | It you need to personally discuss some ideas or issues you can try to get up with 141 | us via the mailing list or the IRC channel. 142 | 143 | * [IRC Channel](irc://irc.freenode.net/rubyworks) / 144 | * [Mailing List](http://googlegroups.com/group/rubyworks-mailinglist) 145 | 146 | ### Git Repository 147 | 148 | The [upstream git repository](http://github.com/rubyworks/anise.git) is 149 | hosted on [GitHub](http://github.com/rubyworks/anise). 150 | 151 | ### Development Requirements 152 | 153 | Anise uses the following development tools. 154 | 155 | * [qed](http://rubyworks.github.com/qed) (test development) 156 | * [ae](http://rubyworks.github.com/ae) (test development) 157 | * [citron](http://rubyworks.github.com/citron) (test development) 158 | * [rubytest-cli](http://rubyworks.github.com/rubytest-cli) (test development) 159 | * [detroit](http://rubyworks.github.com/detroit) (build development) 160 | * [ergo](http://rubyworks.github.com/ergo) (build development) 161 | 162 | ### Test Instructions 163 | 164 | Ainse has two test suites, one using [QED](http://rubyworks.github.com/qed) and 165 | the other using [Citron](http://rubyworks.github.com/citron) which is built on 166 | [RubyTest](http://rubyworks.github.com/rubytest). 167 | 168 | To run the QED demonstrations simple run: 169 | 170 | $ qed 171 | 172 | To run the Citron-based unit tests use: 173 | 174 | $ rubytest 175 | 176 | 177 | ## Authors 178 | 179 | * Trans <[transfire@gmail.com](mailto:transfire@gmail.com)> 180 | 181 | 182 | ## Organizations 183 | 184 | * [Rubyworks](http://rubyworks.github.com) 185 | 186 | 187 | ## Copyrights 188 | 189 | Copyright (c) 2008 Rubyworks. All rights reserved. 190 | 191 | This program is distributed under the terms of the 192 | [BSD-2-Clause](http://www.spdx.org/licenses/BSD-2-Clause) license. 193 | 194 | See LICNESE.txt file for details. 195 | 196 | This project was created on *2008-02-21*. 197 | -------------------------------------------------------------------------------- /test/case_combined_usage.rb: -------------------------------------------------------------------------------- 1 | # The intent of this testcase is test the variant annotaiton modules 2 | # in conjunction, to make sure they can all be used together without 3 | # conflict. 4 | # 5 | # All good intentions aside these cases need some *love*, as they are 6 | # really just other cases thrown together and not really thought out 7 | # as such. Still, something is better than nothing. 8 | 9 | testcase "Combined Usage" do 10 | 11 | context "simple co-existence of annotated methods and attributes" do 12 | 13 | cX = Class.new do 14 | extend Anise::Annotative::Methods 15 | extend Anise::Annotative::Attributes 16 | end 17 | 18 | test "initialize" do 19 | cX.new 20 | end 21 | 22 | end 23 | 24 | context "method annotations work" do 25 | 26 | cX = Class.new do 27 | extend Anise::Annotative::Methods 28 | extend Anise::Annotative::Attributes 29 | 30 | method_annotator :doc 31 | 32 | doc "a-okay captain" 33 | def x; end 34 | end 35 | 36 | test "doc is defined for method" do 37 | cX.ann(:x,:doc) == "a-okay captain" 38 | end 39 | 40 | end 41 | 42 | context "attribute annotations work" do 43 | 44 | cX = Class.new do 45 | extend Anise::Annotative::Methods 46 | extend Anise::Annotative::Attributes 47 | 48 | attr :x, :doc => "still okay" 49 | end 50 | 51 | test "annotation defined for attribute" do 52 | cX.ann(:x,:doc) == "still okay" 53 | end 54 | 55 | end 56 | 57 | context "both method and attribute annotations work" do 58 | 59 | cX = Class.new do 60 | extend Anise::Annotative::Methods 61 | extend Anise::Annotative::Attributes 62 | 63 | method_annotator :doc 64 | 65 | attr :x, :doc => "still okay" 66 | 67 | doc "sure does" 68 | def y; end 69 | end 70 | 71 | test "annotation defined for attribute" do 72 | cX.ann(:x,:doc) == "still okay" 73 | end 74 | 75 | test "doc is defined for method" do 76 | cX.ann(:y,:doc) == "sure does" 77 | end 78 | 79 | end 80 | 81 | context "using an annotation for attribute validation" do 82 | 83 | cX = Class.new do 84 | extend Anise::Annotations 85 | extend Anise::Annotative::Methods 86 | extend Anise::Annotative::Attributes 87 | 88 | attr :a 89 | 90 | ann :@a, :valid => lambda{ |x| x.is_a?(Integer) } 91 | ann :a, :class => Integer 92 | 93 | def initialize(a) 94 | @a = a 95 | end 96 | 97 | def validate 98 | instance_variables.each do |iv| 99 | if validator = self.class.ann(iv)[:valid] 100 | value = instance_variable_get(iv) 101 | unless validator.call(value) 102 | raise "Invalid value #{value} for #{iv}" 103 | end 104 | end 105 | end 106 | end 107 | end 108 | 109 | test "class" do 110 | cX.ann(:a, :class) == Integer 111 | end 112 | 113 | test "validate" do 114 | x = cX.new(1) 115 | x.validate 116 | end 117 | 118 | end 119 | 120 | context "2" do 121 | 122 | cX = Class.new do 123 | extend Anise::Annotations 124 | extend Anise::Annotative::Methods 125 | extend Anise::Annotative::Attributes 126 | 127 | def x1 ; end 128 | 129 | #annotator :ann 130 | ann :x1, :a=>1 131 | ann :x1, :b=>2 132 | end 133 | 134 | test "01" do 135 | cX.ann(:x1,:a) == cX.ann(:x1,:a) 136 | end 137 | 138 | test "02" do 139 | cX.ann(:x1,:a).object_id == cX.ann(:x1,:a).object_id 140 | end 141 | 142 | test "03" do 143 | cX.ann :x1, :a => 2 144 | cX.ann(:x1,:a) == 2 145 | end 146 | 147 | end 148 | 149 | context "3" do 150 | 151 | cX = Class.new do 152 | extend Anise::Annotations 153 | extend Anise::Annotative::Methods 154 | extend Anise::Annotative::Attributes 155 | 156 | def x1 ; end 157 | 158 | #annotator :ann 159 | ann :x1, :a=>1 160 | ann :x1, :b=>2 161 | end 162 | 163 | cY = Class.new(cX) 164 | 165 | test "01" do 166 | cY.ann(:x1,:a) == cY.ann(:x1,:a) 167 | end 168 | 169 | test "02" do 170 | cY.ann(:x1,:a).object_id == cY.ann(:x1,:a).object_id 171 | end 172 | 173 | test "03" do 174 | cY.ann(:x1,:a) == 1 175 | end 176 | 177 | test "04" do 178 | cY.ann(:x1,:b) == 2 179 | end 180 | 181 | test "05" do 182 | cY.ann :x1,:a => 2 183 | cY.ann(:x1,:a) == 2 && 184 | cY.ann(:x1,:b) == 2 185 | end 186 | 187 | end 188 | 189 | context "4" do 190 | 191 | cX = Class.new do 192 | extend Anise::Annotations 193 | extend Anise::Annotative::Methods 194 | extend Anise::Annotative::Attributes 195 | 196 | #annotator :ann 197 | ann :foo, Integer 198 | end 199 | 200 | cY = Class.new(cX) do 201 | ann :foo, String 202 | end 203 | 204 | test "01" do 205 | cX.ann(:foo, :class) == Integer 206 | end 207 | 208 | test "02" do 209 | cY.ann(:foo, :class) == String 210 | end 211 | 212 | end 213 | 214 | context "5" do 215 | 216 | cX = Class.new do 217 | extend Anise::Annotations 218 | extend Anise::Annotative::Methods 219 | extend Anise::Annotative::Attributes 220 | 221 | #annotator :ann 222 | ann :foo, :doc => "hello" 223 | ann :foo, :bar => [] 224 | end 225 | 226 | cY = Class.new(cX) do 227 | ann :foo, :class => String, :doc => "bye" 228 | end 229 | 230 | test "01" do 231 | cX.ann(:foo,:doc) == "hello" 232 | end 233 | 234 | test "02" do 235 | cX.ann(:foo) == {:doc=>"hello", :bar=>[]} 236 | end 237 | 238 | test "03" do 239 | cX.ann(:foo,:bar) << "1" 240 | cX.ann(:foo,:bar) == ["1"] 241 | end 242 | 243 | test "04" do 244 | cY.ann(:foo,:doc) == "bye" 245 | end 246 | 247 | test "05" do 248 | #cY.ann(:foo,:bar) == nil 249 | cY.ann(:foo,:bar) == ["1"] 250 | end 251 | 252 | test "06" do 253 | cY.ann(:foo, :doc => "cap") 254 | cY.ann(:foo, :doc) == "cap" 255 | end 256 | 257 | test "07" do 258 | cY.ann!(:foo,:bar) << "2" 259 | cY.ann(:foo,:bar) == ["1", "2"] && 260 | cX.ann(:foo,:bar) == ["1"] 261 | end 262 | 263 | end 264 | 265 | context "6" do 266 | 267 | cX = Class.new do 268 | extend Anise::Annotations 269 | extend Anise::Annotative::Methods 270 | extend Anise::Annotative::Attributes 271 | 272 | method_annotator :req 273 | 274 | req 'r' 275 | def a ; "a"; end 276 | 277 | req 's', 't' 278 | attr :b 279 | end 280 | 281 | test "annotated" do 282 | cX.ann(:a) == {:req=>'r'} 283 | end 284 | 285 | test "annotated" do 286 | cX.ann(:b) == {:req=>['s','t']} 287 | end 288 | 289 | end 290 | 291 | context "attribute" do 292 | 293 | cX = Class.new do 294 | extend Anise::Annotations 295 | extend Anise::Annotative::Methods 296 | extend Anise::Annotative::Attributes 297 | 298 | attr :q 299 | attr :a, :x => 1 300 | end 301 | 302 | test "a" do 303 | cX.ann(:a) == {:x=>1} 304 | end 305 | 306 | end 307 | 308 | context "attribute using attr" do 309 | 310 | cA = Class.new do 311 | extend Anise::Annotations 312 | extend Anise::Annotative::Methods 313 | extend Anise::Annotative::Attributes 314 | 315 | attr :x, :cast=>"to_s" 316 | end 317 | 318 | test "cast" do 319 | cA.ann(:x,:cast) == "to_s" 320 | end 321 | 322 | end 323 | 324 | context "attribute using attr_accessor" do 325 | 326 | cA = Class.new do 327 | extend Anise::Annotations 328 | extend Anise::Annotative::Methods 329 | extend Anise::Annotative::Attributes 330 | 331 | attr_accessor :x, :cast=>"to_s" 332 | end 333 | 334 | test "instance_attributes" do 335 | iv = cA.instance_attributes - [:taguri] 336 | iv == [:x] 337 | end 338 | 339 | end 340 | 341 | end 342 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | require 'pathname' 5 | 6 | module Indexer 7 | 8 | # Convert index data into a gemspec. 9 | # 10 | # Notes: 11 | # * Assumes all executables are in bin/. 12 | # * Does not yet handle default_executable setting. 13 | # * Does not yet handle platform setting. 14 | # * Does not yet handle required_ruby_version. 15 | # * Support for rdoc entries is weak. 16 | # 17 | class GemspecExporter 18 | 19 | # File globs to include in package --unless a manifest file exists. 20 | FILES = ".index .yardopts alt bin data demo ext features lib man spec test try* [A-Z]*.*" unless defined?(FILES) 21 | 22 | # File globs to omit from FILES. 23 | OMIT = "Config.rb" unless defined?(OMIT) 24 | 25 | # Standard file patterns. 26 | PATTERNS = { 27 | :root => '{.index,Gemfile}', 28 | :bin => 'bin/*', 29 | :lib => 'lib/{**/}*', #.rb', 30 | :ext => 'ext/{**/}extconf.rb', 31 | :doc => '*.{txt,rdoc,md,markdown,tt,textile}', 32 | :test => '{test,spec}/{**/}*.rb' 33 | } unless defined?(PATTERNS) 34 | 35 | # For which revision of indexer spec is this converter intended? 36 | REVISION = 2013 unless defined?(REVISION) 37 | 38 | # 39 | def self.gemspec 40 | new.to_gemspec 41 | end 42 | 43 | # 44 | attr :metadata 45 | 46 | # 47 | def initialize(metadata=nil) 48 | @root_check = false 49 | 50 | if metadata 51 | root_dir = metadata.delete(:root) 52 | if root_dir 53 | @root = root_dir 54 | @root_check = true 55 | end 56 | metadata = nil if metadata.empty? 57 | end 58 | 59 | @metadata = metadata || YAML.load_file(root + '.index') 60 | 61 | if @metadata['revision'].to_i != REVISION 62 | warn "This gemspec exporter was not designed for this revision of index metadata." 63 | end 64 | end 65 | 66 | # 67 | def has_root? 68 | root ? true : false 69 | end 70 | 71 | # 72 | def root 73 | return @root if @root || @root_check 74 | @root_check = true 75 | @root = find_root 76 | end 77 | 78 | # 79 | def manifest 80 | return nil unless root 81 | @manifest ||= Dir.glob(root + 'manifest{,.txt}', File::FNM_CASEFOLD).first 82 | end 83 | 84 | # 85 | def scm 86 | return nil unless root 87 | @scm ||= %w{git hg}.find{ |m| (root + ".#{m}").directory? }.to_sym 88 | end 89 | 90 | # 91 | def files 92 | return [] unless root 93 | @files ||= \ 94 | if manifest 95 | File.readlines(manifest). 96 | map{ |line| line.strip }. 97 | reject{ |line| line.empty? || line[0,1] == '#' } 98 | else 99 | list = [] 100 | Dir.chdir(root) do 101 | FILES.split(/\s+/).each do |pattern| 102 | list.concat(glob(pattern)) 103 | end 104 | OMIT.split(/\s+/).each do |pattern| 105 | list = list - glob(pattern) 106 | end 107 | end 108 | list 109 | end.select{ |path| File.file?(path) }.uniq 110 | end 111 | 112 | # 113 | def glob_files(pattern) 114 | return [] unless root 115 | Dir.chdir(root) do 116 | Dir.glob(pattern).select do |path| 117 | File.file?(path) && files.include?(path) 118 | end 119 | end 120 | end 121 | 122 | def patterns 123 | PATTERNS 124 | end 125 | 126 | def executables 127 | @executables ||= \ 128 | glob_files(patterns[:bin]).map do |path| 129 | File.basename(path) 130 | end 131 | end 132 | 133 | def extensions 134 | @extensions ||= \ 135 | glob_files(patterns[:ext]).map do |path| 136 | File.basename(path) 137 | end 138 | end 139 | 140 | def name 141 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 142 | end 143 | 144 | def homepage 145 | page = ( 146 | metadata['resources'].find{ |r| r['type'] =~ /^home/i } || 147 | metadata['resources'].find{ |r| r['name'] =~ /^home/i } || 148 | metadata['resources'].find{ |r| r['name'] =~ /^web/i } 149 | ) 150 | page ? page['uri'] : false 151 | end 152 | 153 | def licenses 154 | metadata['copyrights'].map{ |c| c['license'] }.compact 155 | end 156 | 157 | def require_paths 158 | paths = metadata['paths'] || {} 159 | paths['load'] || ['lib'] 160 | end 161 | 162 | # 163 | # Convert to gemnspec. 164 | # 165 | def to_gemspec 166 | if has_root? 167 | Gem::Specification.new do |gemspec| 168 | to_gemspec_data(gemspec) 169 | to_gemspec_paths(gemspec) 170 | end 171 | else 172 | Gem::Specification.new do |gemspec| 173 | to_gemspec_data(gemspec) 174 | to_gemspec_paths(gemspec) 175 | end 176 | end 177 | end 178 | 179 | # 180 | # Convert pure data settings. 181 | # 182 | def to_gemspec_data(gemspec) 183 | gemspec.name = name 184 | gemspec.version = metadata['version'] 185 | gemspec.summary = metadata['summary'] 186 | gemspec.description = metadata['description'] 187 | 188 | metadata['authors'].each do |author| 189 | gemspec.authors << author['name'] 190 | 191 | if author.has_key?('email') 192 | if gemspec.email 193 | gemspec.email << author['email'] 194 | else 195 | gemspec.email = [author['email']] 196 | end 197 | end 198 | end 199 | 200 | gemspec.licenses = licenses 201 | 202 | requirements = metadata['requirements'] || [] 203 | requirements.each do |req| 204 | next if req['optional'] 205 | next if req['external'] 206 | 207 | name = req['name'] 208 | groups = req['groups'] || [] 209 | 210 | version = gemify_version(req['version']) 211 | 212 | if groups.empty? or groups.include?('runtime') 213 | # populate runtime dependencies 214 | if gemspec.respond_to?(:add_runtime_dependency) 215 | gemspec.add_runtime_dependency(name,*version) 216 | else 217 | gemspec.add_dependency(name,*version) 218 | end 219 | else 220 | # populate development dependencies 221 | if gemspec.respond_to?(:add_development_dependency) 222 | gemspec.add_development_dependency(name,*version) 223 | else 224 | gemspec.add_dependency(name,*version) 225 | end 226 | end 227 | end 228 | 229 | # convert external dependencies into gemspec requirements 230 | requirements.each do |req| 231 | next unless req['external'] 232 | gemspec.requirements << ("%s-%s" % req.values_at('name', 'version')) 233 | end 234 | 235 | gemspec.homepage = homepage 236 | gemspec.require_paths = require_paths 237 | gemspec.post_install_message = metadata['install_message'] 238 | end 239 | 240 | # 241 | # Set gemspec settings that require a root directory path. 242 | # 243 | def to_gemspec_paths(gemspec) 244 | gemspec.files = files 245 | gemspec.extensions = extensions 246 | gemspec.executables = executables 247 | 248 | if Gem::VERSION < '1.7.' 249 | gemspec.default_executable = gemspec.executables.first 250 | end 251 | 252 | gemspec.test_files = glob_files(patterns[:test]) 253 | 254 | unless gemspec.files.include?('.document') 255 | gemspec.extra_rdoc_files = glob_files(patterns[:doc]) 256 | end 257 | end 258 | 259 | # 260 | # Return a copy of this file. This is used to generate a local 261 | # .gemspec file that can automatically read the index file. 262 | # 263 | def self.source_code 264 | File.read(__FILE__) 265 | end 266 | 267 | private 268 | 269 | def find_root 270 | root_files = patterns[:root] 271 | if Dir.glob(root_files).first 272 | Pathname.new(Dir.pwd) 273 | elsif Dir.glob("../#{root_files}").first 274 | Pathname.new(Dir.pwd).parent 275 | else 276 | #raise "Can't find root of project containing `#{root_files}'." 277 | warn "Can't find root of project containing `#{root_files}'." 278 | nil 279 | end 280 | end 281 | 282 | def glob(pattern) 283 | if File.directory?(pattern) 284 | Dir.glob(File.join(pattern, '**', '*')) 285 | else 286 | Dir.glob(pattern) 287 | end 288 | end 289 | 290 | def gemify_version(version) 291 | case version 292 | when /^(.*?)\+$/ 293 | ">= #{$1}" 294 | when /^(.*?)\-$/ 295 | "< #{$1}" 296 | when /^(.*?)\~$/ 297 | "~> #{$1}" 298 | else 299 | version 300 | end 301 | end 302 | 303 | end 304 | 305 | end 306 | 307 | Indexer::GemspecExporter.gemspec -------------------------------------------------------------------------------- /work/deprecated/annotations.rb: -------------------------------------------------------------------------------- 1 | # = Annotation 2 | # 3 | # Annotations allows you to annontate objects, including methods with arbitrary 4 | # "metadata". These annotations don't do anything in themselves. They are 5 | # merely comments. But you can put them to use. For instance an attribute 6 | # validator might check for an annotation called :valid and test against it. 7 | # 8 | # Annotation is an OpenObject, and is used across the board for keeping annotations. 9 | # 10 | # Annotation class serves for both simple and inherited cases depending on whether 11 | # a base class is given. 12 | # 13 | # class X 14 | # attr :a 15 | # ann :@a, :valid => lambda{ |x| x.is_a?(Integer) } 16 | # 17 | # def validate 18 | # instance_variables.each { |iv| 19 | # if validator = self.class.ann(iv)[:valid] 20 | # value = instance_variable_get(iv) 21 | # unless validator.call(vale) 22 | # raise "Invalid value #{value} for #{iv}" 23 | # end 24 | # end 25 | # } 26 | # end 27 | # 28 | # end 29 | # 30 | # == Authors 31 | # 32 | # * Thomas Sawyer 33 | # * George Moschovitis 34 | # 35 | # == History 36 | # 37 | # * 2006-11-07 trans Created this ultra-concise version of annotations. 38 | # 39 | # == Todo 40 | # 41 | # * Might be nice to have a default object of annotation, eg. the next 42 | # method defined, like how +desc+ annotates a rake +task+. 43 | # * The ann(x).name notation is kind of nice. Would like to add that 44 | # back-in if reasonable. Basically this require heritage to be an OpenObject 45 | # rather than just a hash. 46 | # 47 | # == Copying 48 | # 49 | # Copyright (c) 2005 Thomas Sawyer, George Moschovitis 50 | # 51 | # Ruby License 52 | # 53 | # This module is free software. You may use, modify, and/or redistribute this 54 | # software under the same terms as Ruby. 55 | # 56 | # This program is distributed in the hope that it will be useful, but WITHOUT 57 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 58 | # FOR A PARTICULAR PURPOSE. 59 | 60 | require 'facets/to_h' # Hash#to_h 61 | require 'facets/hash/rekey' 62 | require 'facets/hash/op' 63 | 64 | # = Annotation 65 | # 66 | # Annotations allows you to annontate objects, including methods with arbitrary 67 | # "metadata". These annotations don't do anything in themselves. They are 68 | # merely comments. But you can put them to use. For instance an attribute 69 | # validator might check for an annotation called :valid and test against it. 70 | # 71 | # Annotation is an OpenObject, and is used across the board for keeping annotations. 72 | # 73 | # Annotation class serves for both simple and inherited cases depending on whether 74 | # a base class is given. 75 | # 76 | # == Synopsis 77 | # 78 | # class X 79 | # attr :a 80 | # ann :@a, :valid => lambda{ |x| x.is_a?(Integer) } 81 | # 82 | # def validate 83 | # instance_variables.each { |iv| 84 | # if validator = self.class.ann(iv)[:valid] 85 | # value = instance_variable_get(iv) 86 | # unless validator.call(vale) 87 | # raise "Invalid value #{value} for #{iv}" 88 | # end 89 | # end 90 | # } 91 | # end 92 | # 93 | # end 94 | 95 | #-- 96 | # By using a global veriable rather the definining a class instance variable 97 | # for each class/module, it is possible to quicky scan all annotations for the 98 | # entire system. To do the same without this would require scanning through 99 | # the ObjectSpace. (Still which is better?) 100 | # 101 | #$annotations = Hash.new { |h,k| h[k] = {} } 102 | #++ 103 | 104 | class Module 105 | 106 | def annotations 107 | #$annotations[self] 108 | @annotations ||= {} 109 | end 110 | 111 | def heritage(ref) 112 | ref = ref.to_sym 113 | ancestors.inject({}) { |memo, ancestor| 114 | ancestor.annotations[ref] ||= {} 115 | ancestor.annotations[ref] + memo 116 | } 117 | end 118 | 119 | # Set or read annotations. 120 | 121 | def ann( ref, keys_or_class=nil, keys=nil ) 122 | return heritage(ref) unless keys_or_class or keys 123 | 124 | if Class === keys_or_class 125 | keys ||= {} 126 | keys[:class] = keys_or_class 127 | else 128 | keys = keys_or_class 129 | end 130 | 131 | if Hash === keys 132 | ref = ref.to_sym 133 | annotations[ref] ||= {} 134 | annotations[ref].update(keys.rekey) 135 | else 136 | key = keys.to_sym 137 | heritage(ref)[key] 138 | end 139 | end 140 | 141 | # To change an annotation's value in place for a given class or module 142 | # it first must be duplicated, otherwise the change may effect annotations 143 | # in the class or module's ancestors. 144 | 145 | def ann!( ref, keys_or_class=nil, keys=nil ) 146 | #return heritage(ref) unless keys_or_class or keys 147 | return annotations[ref] unless keys_or_class or keys 148 | 149 | if Class === keys_or_class 150 | keys ||= {} 151 | keys[:class] = keys_or_class 152 | else 153 | keys = keys_or_class 154 | end 155 | 156 | if Hash === keys 157 | ref = ref.to_sym 158 | annotations[ref] ||= {} 159 | annotations[ref].update(keys.rekey) 160 | else 161 | key = keys.to_sym 162 | annotations[ref][key] = heritage(ref)[key].dup 163 | end 164 | end 165 | 166 | end 167 | 168 | # TITLE 169 | # 170 | # Annotated Attributes 171 | # 172 | # DESCRIPTION: 173 | # 174 | # This framework modifies the attr_* methods to allow easy 175 | # addition of annotations. 176 | # 177 | # COPYRIGHT: 178 | # 179 | # Copyright (c) 2005 Thomas Sawyer 180 | # 181 | # LICENSE: 182 | # 183 | # Ruby License 184 | # 185 | # This module is free software. You may use, modify, and/or redistribute this 186 | # software under the same terms as Ruby. 187 | # 188 | # This program is distributed in the hope that it will be useful, but WITHOUT 189 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 190 | # FOR A PARTICULAR PURPOSE. 191 | # 192 | # AUTHORS: 193 | # 194 | # - Thomas Sawyer 195 | # - George Moschovitis 196 | 197 | require 'facets/annotations.rb' 198 | require 'facets/inheritor.rb' 199 | 200 | # = Annotated Attributes 201 | # 202 | # This framework modifies the attr_* methods to allow easy addition of annotations. 203 | # It the built in attribute methods (attr, attr_reader, attr_writer and attr_accessor), 204 | # to allow annotations to added to them directly rather than requireing a separate 205 | # #ann statement. 206 | # 207 | # class X 208 | # attr :a, :valid => lambda{ |x| x.is_a?(Integer) } 209 | # end 210 | # 211 | # See annotation.rb for more information. 212 | # 213 | # NOTE This library was designed to be backward compatible with the 214 | # standard versions of the same methods. 215 | 216 | class ::Module 217 | 218 | inheritor :instance_attributes, [], :| 219 | 220 | def attr( *args ) 221 | args.flatten! 222 | case args.last 223 | when TrueClass 224 | args.pop 225 | attr_accessor( *args ) 226 | when FalseClass 227 | args.pop 228 | attr_reader( *args ) 229 | else 230 | attr_reader( *args ) 231 | end 232 | end 233 | 234 | alias :plain_reader :attr_reader 235 | alias :plain_writer :attr_writer 236 | alias :plain_accessor :attr_accessor 237 | 238 | code = '' 239 | 240 | [ :_reader, :_writer, :_accessor].each do |m| 241 | 242 | code << %{ 243 | def attr#{m}(*args) 244 | args.flatten! 245 | 246 | harg={}; while args.last.is_a?(Hash) 247 | harg.update(args.pop) 248 | end 249 | 250 | raise ArgumentError if args.empty? and harg.empty? 251 | 252 | if args.empty? # hash mode 253 | harg.each { |a,h| attr#{m}(a,h) } 254 | else 255 | klass = harg[:class] = args.pop if args.last.is_a?(Class) 256 | 257 | args.each { |a| 258 | plain#{m} a 259 | a = a.to_sym 260 | ann(a,harg) 261 | } 262 | instance_attributes!.concat( args ) #merge! 263 | 264 | # Use this callback to customize for your needs. 265 | if respond_to?(:attr_callback) 266 | attr_callback(self, args, harg) 267 | end 268 | 269 | # return the names of the attributes created 270 | return args 271 | end 272 | end 273 | } 274 | 275 | end 276 | 277 | class_eval( code ) 278 | 279 | # TODO Should attribute alias be kept? 280 | alias_method :attribute, :attr_accessor 281 | 282 | # Return list of attributes that have a :class annotation. 283 | # 284 | # class MyClass 285 | # attr_accessor :test 286 | # attr_accessor :name, String, :doc => 'Hello' 287 | # attr_accessor :age, Fixnum 288 | # end 289 | # 290 | # MyClass.instance_attributes # => [:test, :name, :age, :body] 291 | # MyClass.classified_attributes # => [:name, :age] 292 | 293 | def classified_attributes 294 | instance_attributes.find_all do |a| 295 | self.ann(a, :class) 296 | end 297 | end 298 | 299 | end 300 | 301 | -------------------------------------------------------------------------------- /work/historic/annotations.rb: -------------------------------------------------------------------------------- 1 | # = Annotation 2 | # 3 | # Annotations allows you to annontate objects, including methods with arbitrary 4 | # "metadata". These annotations don't do anything in themselves. They are 5 | # merely comments. But you can put them to use. For instance an attribute 6 | # validator might check for an annotation called :valid and test against it. 7 | # 8 | # Annotation is an OpenObject, and is used across the board for keeping annotations. 9 | # 10 | # Annotation class serves for both simple and inherited cases depending on whether 11 | # a base class is given. 12 | # 13 | # class X 14 | # attr :a 15 | # ann :@a, :valid => lambda{ |x| x.is_a?(Integer) } 16 | # 17 | # def validate 18 | # instance_variables.each { |iv| 19 | # if validator = self.class.ann(iv)[:valid] 20 | # value = instance_variable_get(iv) 21 | # unless validator.call(vale) 22 | # raise "Invalid value #{value} for #{iv}" 23 | # end 24 | # end 25 | # } 26 | # end 27 | # 28 | # end 29 | # 30 | # == Authors 31 | # 32 | # * Thomas Sawyer 33 | # * George Moschovitis 34 | # 35 | # == History 36 | # 37 | # * 2006-11-07 trans Created this ultra-concise version of annotations. 38 | # 39 | # == Todo 40 | # 41 | # * Might be nice to have a default object of annotation, eg. the next 42 | # method defined, like how +desc+ annotates a rake +task+. 43 | # * The ann(x).name notation is kind of nice. Would like to add that 44 | # back-in if reasonable. Basically this require heritage to be an OpenObject 45 | # rather than just a hash. 46 | # 47 | # == Copying 48 | # 49 | # Copyright (c) 2005 Thomas Sawyer, George Moschovitis 50 | # 51 | # Ruby License 52 | # 53 | # This module is free software. You may use, modify, and/or redistribute this 54 | # software under the same terms as Ruby. 55 | # 56 | # This program is distributed in the hope that it will be useful, but WITHOUT 57 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 58 | # FOR A PARTICULAR PURPOSE. 59 | 60 | warn "Libary facets/annotations.rb will be deprecated and spun spun off as new project." 61 | 62 | require 'facets/hash/to_h' 63 | require 'facets/hash/rekey' 64 | require 'facets/hash/op' 65 | 66 | # = Annotation 67 | # 68 | # Annotations allows you to annontate objects, including methods with arbitrary 69 | # "metadata". These annotations don't do anything in themselves. They are 70 | # merely comments. But you can put them to use. For instance an attribute 71 | # validator might check for an annotation called :valid and test against it. 72 | # 73 | # Annotation is an OpenObject, and is used across the board for keeping annotations. 74 | # 75 | # Annotation class serves for both simple and inherited cases depending on whether 76 | # a base class is given. 77 | # 78 | # == Synopsis 79 | # 80 | # class X 81 | # attr :a 82 | # ann :@a, :valid => lambda{ |x| x.is_a?(Integer) } 83 | # 84 | # def validate 85 | # instance_variables.each { |iv| 86 | # if validator = self.class.ann(iv)[:valid] 87 | # value = instance_variable_get(iv) 88 | # unless validator.call(vale) 89 | # raise "Invalid value #{value} for #{iv}" 90 | # end 91 | # end 92 | # } 93 | # end 94 | # 95 | # end 96 | 97 | #-- 98 | # By using a global veriable rather the definining a class instance variable 99 | # for each class/module, it is possible to quicky scan all annotations for the 100 | # entire system. To do the same without this would require scanning through 101 | # the ObjectSpace. (Still which is better?) 102 | # 103 | #$annotations = Hash.new { |h,k| h[k] = {} } 104 | #++ 105 | 106 | class Module 107 | 108 | def annotations 109 | #$annotations[self] 110 | @annotations ||= {} 111 | end 112 | 113 | def heritage(ref) 114 | ref = ref.to_sym 115 | ancestors.inject({}) { |memo, ancestor| 116 | ancestor.annotations[ref] ||= {} 117 | ancestor.annotations[ref] + memo 118 | } 119 | end 120 | 121 | # Set or read annotations. 122 | 123 | def ann( ref, keys_or_class=nil, keys=nil ) 124 | return heritage(ref) unless keys_or_class or keys 125 | 126 | if Class === keys_or_class 127 | keys ||= {} 128 | keys[:class] = keys_or_class 129 | else 130 | keys = keys_or_class 131 | end 132 | 133 | if Hash === keys 134 | ref = ref.to_sym 135 | annotations[ref] ||= {} 136 | annotations[ref].update(keys.rekey) 137 | else 138 | key = keys.to_sym 139 | heritage(ref)[key] 140 | end 141 | end 142 | 143 | # To change an annotation's value in place for a given class or module 144 | # it first must be duplicated, otherwise the change may effect annotations 145 | # in the class or module's ancestors. 146 | 147 | def ann!( ref, keys_or_class=nil, keys=nil ) 148 | #return heritage(ref) unless keys_or_class or keys 149 | return annotations[ref] unless keys_or_class or keys 150 | 151 | if Class === keys_or_class 152 | keys ||= {} 153 | keys[:class] = keys_or_class 154 | else 155 | keys = keys_or_class 156 | end 157 | 158 | if Hash === keys 159 | ref = ref.to_sym 160 | annotations[ref] ||= {} 161 | annotations[ref].update(keys.rekey) 162 | else 163 | key = keys.to_sym 164 | annotations[ref][key] = heritage(ref)[key].dup 165 | end 166 | end 167 | 168 | end 169 | 170 | # TITLE 171 | # 172 | # Annotated Attributes 173 | # 174 | # DESCRIPTION: 175 | # 176 | # This framework modifies the attr_* methods to allow easy 177 | # addition of annotations. 178 | # 179 | # COPYRIGHT: 180 | # 181 | # Copyright (c) 2005 Thomas Sawyer 182 | # 183 | # LICENSE: 184 | # 185 | # Ruby License 186 | # 187 | # This module is free software. You may use, modify, and/or redistribute this 188 | # software under the same terms as Ruby. 189 | # 190 | # This program is distributed in the hope that it will be useful, but WITHOUT 191 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 192 | # FOR A PARTICULAR PURPOSE. 193 | # 194 | # AUTHORS: 195 | # 196 | # - Thomas Sawyer 197 | # - George Moschovitis 198 | 199 | require 'facets/annotations.rb' 200 | require 'facets/inheritor.rb' 201 | 202 | # = Annotated Attributes 203 | # 204 | # This framework modifies the attr_* methods to allow easy addition of annotations. 205 | # It the built in attribute methods (attr, attr_reader, attr_writer and attr_accessor), 206 | # to allow annotations to added to them directly rather than requireing a separate 207 | # #ann statement. 208 | # 209 | # class X 210 | # attr :a, :valid => lambda{ |x| x.is_a?(Integer) } 211 | # end 212 | # 213 | # See annotation.rb for more information. 214 | # 215 | # NOTE This library was designed to be backward compatible with the 216 | # standard versions of the same methods. 217 | 218 | class ::Module 219 | 220 | inheritor :instance_attributes, [], :| 221 | 222 | def attr( *args ) 223 | args.flatten! 224 | case args.last 225 | when TrueClass 226 | args.pop 227 | attr_accessor( *args ) 228 | when FalseClass 229 | args.pop 230 | attr_reader( *args ) 231 | else 232 | attr_reader( *args ) 233 | end 234 | end 235 | 236 | alias :plain_reader :attr_reader 237 | alias :plain_writer :attr_writer 238 | alias :plain_accessor :attr_accessor 239 | 240 | code = '' 241 | 242 | [ :_reader, :_writer, :_accessor].each do |m| 243 | 244 | code << %{ 245 | def attr#{m}(*args) 246 | args.flatten! 247 | 248 | harg={}; while args.last.is_a?(Hash) 249 | harg.update(args.pop) 250 | end 251 | 252 | raise ArgumentError if args.empty? and harg.empty? 253 | 254 | if args.empty? # hash mode 255 | harg.each { |a,h| attr#{m}(a,h) } 256 | else 257 | klass = harg[:class] = args.pop if args.last.is_a?(Class) 258 | 259 | args.each { |a| 260 | plain#{m} a 261 | a = a.to_sym 262 | ann(a,harg) 263 | } 264 | instance_attributes!.concat( args ) #merge! 265 | 266 | # Use this callback to customize for your needs. 267 | if respond_to?(:attr_callback) 268 | attr_callback(self, args, harg) 269 | end 270 | 271 | # return the names of the attributes created 272 | return args 273 | end 274 | end 275 | } 276 | 277 | end 278 | 279 | class_eval( code ) 280 | 281 | # TODO Should attribute alias be kept? 282 | alias_method :attribute, :attr_accessor 283 | 284 | # Return list of attributes that have a :class annotation. 285 | # 286 | # class MyClass 287 | # attr_accessor :test 288 | # attr_accessor :name, String, :doc => 'Hello' 289 | # attr_accessor :age, Fixnum 290 | # end 291 | # 292 | # MyClass.instance_attributes # => [:test, :name, :age, :body] 293 | # MyClass.classified_attributes # => [:name, :age] 294 | 295 | def classified_attributes 296 | instance_attributes.find_all do |a| 297 | self.ann(a, :class) 298 | end 299 | end 300 | 301 | end 302 | 303 | -------------------------------------------------------------------------------- /work/deprecated/annotation-old.rb: -------------------------------------------------------------------------------- 1 | # = annotation.rb 2 | # 3 | # == Copyright (c) 2005 Thomas Sawyer 4 | # 5 | # Ruby License 6 | # 7 | # This module is free software. You may use, modify, and/or redistribute this 8 | # software under the same terms as Ruby. 9 | # 10 | # This program is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | # FOR A PARTICULAR PURPOSE. 13 | # 14 | # == Authors & Contributors 15 | # 16 | # * Thomas Sawyer 17 | 18 | # Author:: Thomas Sawyer 19 | # Copyright:: Copyright (c) 2005 Thomas Sawyer 20 | # License:: Ruby License 21 | 22 | require 'facets/core/hash/to_h' 23 | require 'facets/core/kernel/object_class' 24 | require 'facets/more/openobject' 25 | #require 'facets/more/nullclass' 26 | 27 | # = Annotation 28 | # 29 | # Annotations allows you to annontate objects, including methods with arbitrary 30 | # "metadata". These annotations don't do anything in themselves. They are 31 | # merely comments. But you can put them to use. For instance an attribute 32 | # validator might check for an annotation called :valid and test against it. 33 | # 34 | # Annotation is an OpenObject, and is used across the board for keeping annotations. 35 | # 36 | # Annotation class serves for both simple and inherited cases depending on whether 37 | # a base class is given. 38 | # 39 | # == Synopsis 40 | # 41 | # class X 42 | # attr :a 43 | # ann :@a, :valid => lambda{ |x| x.is_a?(Integer) } 44 | # 45 | # def validate 46 | # instance_variables.each { |iv| 47 | # if validator = self.class.ann(iv)[:valid] 48 | # value = instance_variable_get(iv) 49 | # unless validator.call(vale) 50 | # raise "Invalid value #{value} for #{iv}" 51 | # end 52 | # end 53 | # } 54 | # end 55 | # 56 | # end 57 | 58 | class Annotations 59 | 60 | def initialize( base ) 61 | @base, @ann = base, {} 62 | end 63 | 64 | def [](key) 65 | key = :self if key == @base 66 | @ann[key] ||= Annotation.new(@base, key) 67 | end 68 | 69 | def []=(key, note) 70 | raise ArgumentError unless Annotation === note 71 | @ann[key] = note 72 | end 73 | 74 | def ==(other) @ann == other.to_h end 75 | 76 | def to_h() @ann end 77 | 78 | def key?(key) @ann.key?(key) end 79 | 80 | def each(&yld) 81 | @ann.each(&yld) 82 | end 83 | 84 | def update( other ) 85 | @ann.update( other.to_h ) 86 | self 87 | end 88 | private :update 89 | 90 | def self 91 | self[:self] 92 | end 93 | 94 | # def inheritance 95 | # anns = Annotations.new(@base) 96 | # @base.ancestors.each do |anc| 97 | # anc.annotations.each { |k,v| 98 | # next if anns.key?(k) 99 | # anns[k] = v #.dup 100 | # } 101 | # end 102 | # #@ann.each_key do |name| 103 | # # anns[name] = self[name].heritage 104 | # #end 105 | # return anns 106 | # end 107 | 108 | def annotated_base() @base end 109 | 110 | def method_missing( key, *args, &blk ) 111 | if key?(key) 112 | self[key] #.heritage 113 | else 114 | if @base.ancestors.any?{|anc| anc.annotations.key?(key)} 115 | return self[key].heritage 116 | #ann[name].heritage 117 | end 118 | super 119 | end 120 | end 121 | 122 | end 123 | 124 | # 125 | 126 | class Annotation < OpenObject 127 | 128 | def initialize( base, key, orig=nil ) 129 | @base = base 130 | @key = key 131 | @orig = orig || self 132 | super() 133 | end 134 | 135 | def original 136 | @orig 137 | end 138 | 139 | #def class 140 | # self[:class] 141 | #end 142 | 143 | def inspect 144 | "#<#{object_class.name}(#{@base}##{@key}) #{super}>" 145 | end 146 | 147 | def annotation_key 148 | @key 149 | end 150 | 151 | def heritage( orig=nil ) 152 | ah = {} 153 | @base.ancestors.reverse_each do |anc| 154 | if anc.annotations.key?(@key) 155 | anc.annotations[@key].each { |k,v| 156 | case v 157 | when Module 158 | ah[k] = v 159 | else 160 | ah[k] = v.dup rescue v 161 | end 162 | } 163 | end 164 | end 165 | a = Annotation.new( @base, @key, orig || @orig ) 166 | a.send(:replace, ah) 167 | a 168 | end 169 | 170 | def method_missing( sym, *args, &blk ) 171 | type = sym.to_s[-1,1] 172 | key = sym.to_s.gsub(/[=!?]$/, '').to_sym 173 | 174 | case type 175 | when '=' 176 | @orig[key] = args[0] #, *args, &blk ) 177 | when '!' 178 | #r = super( key, *args, &blk ) 179 | if key?(key) 180 | self[key] 181 | else 182 | self[key] = heritage(self)[key] 183 | end 184 | else 185 | heritage[sym] 186 | end 187 | end 188 | 189 | end 190 | 191 | # 192 | 193 | class Module 194 | 195 | def annotations 196 | @annotations ||= Annotations.new(self) 197 | end 198 | 199 | def ann( key=nil, *options ) 200 | return annotations unless key 201 | return name.collect{|k,v| ann k,*v} if Hash === key 202 | return annotations[key] if options.empty? 203 | opt = {} 204 | opt.update options.pop if Hash === options.last 205 | opt[:class] = options.pop if Class === options.last 206 | keys = [key].concat options 207 | keys.each do |key| 208 | note = annotations[key] 209 | #unless options.empty? 210 | # (hopt[:tags] ||= []) << options 211 | #end 212 | note.send(:update,opt) 213 | end 214 | keys 215 | end 216 | 217 | end 218 | 219 | 220 | # Any object can be annotated. 221 | 222 | module Kernel 223 | 224 | #-- 225 | # Store and retrieve annotations for an object. 226 | #def annotation( harg=nil ) 227 | # @_annotation ||= Annotation.new 228 | # return @_annotation unless harg 229 | # @_annotation.send(:update, harg) 230 | #end 231 | #++ 232 | 233 | #-- 234 | # Not sure about this. 235 | #++ 236 | def ann( *args ) 237 | #if singleton_class? 238 | (class << self; self; end).ann( *args ) 239 | #else 240 | # self.class.ann( *args ) 241 | #end 242 | end 243 | 244 | end 245 | 246 | 247 | # _____ _ 248 | # |_ _|__ ___| |_ 249 | # | |/ _ \/ __| __| 250 | # | | __/\__ \ |_ 251 | # |_|\___||___/\__| 252 | # 253 | 254 | =begin test 255 | 256 | require 'test/unit' 257 | 258 | class TestAnnotation1 < Test::Unit::TestCase 259 | class X 260 | def x1 ; end 261 | ann :x1, :a=>1 262 | ann :x1, :b=>2 263 | end 264 | 265 | def test_1_01 266 | assert_equal(X.ann.x1.orig.object_id, X.ann.x1.orig.object_id) 267 | end 268 | def test_1_02 269 | X.ann.x1.a = 2 270 | assert_equal(2, X.ann.x1.a) 271 | end 272 | end 273 | 274 | class TestAnnotation2 < Test::Unit::TestCase 275 | class X 276 | def x1 ; end 277 | ann :x1, :a=>1 278 | ann :x1, :b=>2 279 | end 280 | class Y < X ; end 281 | 282 | def test_2_01 283 | assert_equal(Y.ann.x1.original.object_id, Y.ann.x1.original.object_id) 284 | end 285 | def test_2_02 286 | assert_equal(1, Y.ann.x1.a) 287 | assert_equal(2, Y.ann.x1.b) 288 | end 289 | def test_2_03 290 | Y.ann.x1.a = 2 291 | assert_equal(2, Y.ann.x1.a) 292 | assert_equal(2, Y.ann.x1.b) 293 | end 294 | end 295 | 296 | class TestAnnotation3 < Test::Unit::TestCase 297 | class X 298 | def x1 ; end 299 | ann :x1, :a=>1 300 | ann :x1, :b=>2 301 | end 302 | class Y < X 303 | ann :x1, :y=>'Y' 304 | end 305 | 306 | def test_3_01 307 | assert_equal(Y.ann.x1.original.object_id, Y.ann.x1.original.object_id) 308 | end 309 | def test_3_02 310 | assert_equal(1, Y.ann.x1.a) 311 | end 312 | def test_3_03 313 | Y.ann.x1.a = 2 314 | assert_equal(2, Y.ann.x1.a) 315 | end 316 | end 317 | 318 | class TestAnnotation4 < Test::Unit::TestCase 319 | module M 320 | def x1 ; end 321 | ann :x1, :a=>1 322 | ann :x1, :b=>2 323 | end 324 | class X 325 | include M 326 | end 327 | 328 | def test_4_01 329 | assert_equal(X.ann.x1.original.object_id, X.ann.x1.original.object_id) 330 | end 331 | def test_4_02 332 | assert_equal(1, X.ann.x1.a) 333 | assert_equal(2, X.ann.x1.b) 334 | end 335 | def test_4_03 336 | X.ann.x1.a = 2 337 | assert_equal(2, X.ann.x1.a) 338 | end 339 | end 340 | 341 | class TestAnnotation5 < Test::Unit::TestCase 342 | class X 343 | ann :foo, :doc => "hello" 344 | ann :foo, :bar => [] 345 | end 346 | class Y < X 347 | ann :foo, :class => String, :doc => "bye" 348 | end 349 | 350 | def test_5_01 351 | assert_equal( "hello", X.ann(:foo).doc ) 352 | end 353 | def test_5_02 354 | assert_equal( X.ann(:foo), X.ann.foo ) 355 | end 356 | def test_5_03 357 | X.ann(:foo).bar! << "1" 358 | assert_equal( ["1"], X.ann.foo.bar ) 359 | end 360 | def test_5_04 361 | assert_equal( "bye", Y.ann(:foo).doc ) 362 | end 363 | def test_5_05 364 | #assert_equal( nil, Y.ann(:foo).bar ) 365 | assert_equal( ["1"], Y.ann(:foo).bar ) 366 | end 367 | def test_5_06 368 | Y.ann(:foo).doc = "cap" 369 | assert_equal( "cap", Y.ann(:foo).doc ) 370 | end 371 | def test_5_07 372 | Y.ann.foo.doc = "cap2" 373 | assert_equal( "cap2", Y.ann(:foo).doc ) 374 | end 375 | def test_5_08 376 | Y.ann(:foo).bar! << "2" 377 | assert_equal( ["1", "2"], Y.ann(:foo).bar ) 378 | assert_equal( ["1", "2"], Y.ann(:foo).bar! ) 379 | assert_equal( ["1"], X.ann(:foo).bar ) 380 | end 381 | end 382 | 383 | # Test non-existent elements. 384 | 385 | class TestAnnotation6 < Test::Unit::TestCase 386 | class X 387 | def x1 ; end 388 | ann :x1, :a=>1, :b=>2 389 | end 390 | 391 | def test_6_01 392 | assert_equal( 1, X.ann(:x1)[:a] ) 393 | assert_equal( 2, X.ann(:x1)[:b] ) 394 | assert_equal( {}, X.ann(:x2) ) 395 | end 396 | def test_6_02 397 | assert_equal( 1, X.ann(:x1)[:a] ) 398 | assert_equal( 2, X.ann(:x1)[:b] ) 399 | assert_equal( nil, X.ann(:x1)[:c] ) 400 | end 401 | def test_6_03 402 | assert_equal( 1, X.ann.x1[:a] ) 403 | assert_equal( 2, X.ann.x1[:b] ) 404 | end 405 | def test_6_04 406 | assert_equal( 1, X.ann.x1.a ) 407 | assert_equal( 2, X.ann.x1.b ) 408 | end 409 | def test_6_05 410 | assert_equal( {}, X.ann.x2 ) 411 | assert_equal( nil, X.ann.x1.r ) 412 | end 413 | end 414 | 415 | class TestAnnotation7 < Test::Unit::TestCase 416 | class X 417 | def x ; end 418 | ann :x, :z=>1 419 | end 420 | class Y < X ; end 421 | 422 | def test_7_04 423 | assert_equal(1, X.ann(:x)[:z]) 424 | assert_equal(nil, Y.ann(:x)[:z]) 425 | assert_equal(1, Y.ann(:x).z) 426 | end 427 | def test_7_05 428 | assert_equal(1, X.ann.x[:z]) 429 | assert_equal(nil, Y.ann.x[:z]) 430 | assert_equal(1, Y.ann.x.z) 431 | end 432 | def test_7_06 433 | Y.ann.x.z = 2 434 | assert_equal(2, Y.ann.x.z) 435 | end 436 | end 437 | 438 | class TestAnnotation8 < Test::Unit::TestCase 439 | class X 440 | def n ; end 441 | ann :n, :v=>1 442 | ann :n, :p=>3 443 | end 444 | class Y < X 445 | ann :n, :b=>2 446 | end 447 | 448 | def test_08_01 449 | assert_equal(2, Y.ann(:n)[:b]) 450 | assert_equal(nil, Y.ann(:n)[:v]) 451 | assert_equal(1, Y.ann(:n).v) 452 | end 453 | def test_08_02 454 | assert_equal(2, Y.ann(:n).b) 455 | end 456 | def test_08_03 457 | assert_equal(2, Y.ann.n.b) 458 | end 459 | end 460 | 461 | class TestAnnotation9 < Test::Unit::TestCase 462 | module M 463 | ann :a, String 464 | ann :b, :foo=>3 465 | end 466 | 467 | def test_09_01 468 | assert_equal( String, M.ann.a[:class] ) 469 | end 470 | def test_09_02 471 | assert_equal( String, M.ann.a.class ) 472 | end 473 | def test_09_03 474 | assert_equal(3, M.ann.b.foo) 475 | end 476 | end 477 | 478 | class TestAnnotation10 < Test::Unit::TestCase 479 | class C 480 | ann :self, :mod => 'YES' 481 | end 482 | def test_10_01 483 | assert_equal( 'YES', C.ann.self.mod ) 484 | end 485 | end 486 | 487 | # class TestAnnotation10 < Test::Unit::TestCase 488 | # module M 489 | # ann :this, :koko => [] 490 | # ann.this.koko! << 1 491 | # end 492 | # class C1 493 | # #ann :this, :koko => [] 494 | # include M 495 | # ann.this.koko! << 2 496 | # ann.this.koko! << 3 497 | # end 498 | # class C2 499 | # include M 500 | # ann.this.koko! << 4 501 | # end 502 | # 503 | # def test_10_01 504 | # assert_equal( [1], M.ann.this.koko ) 505 | # end 506 | # def test_10_02 507 | # assert_equal( [1,2,3], C1.ann.this.koko ) 508 | # end 509 | # def test_10_03 510 | # assert_equal( [1,4], C2.ann.this.koko ) 511 | # end 512 | # end 513 | 514 | # class TC07 < Test::Unit::TestCase 515 | # class K 516 | # ann self, :oid => 'key' 517 | # ann :w, String 518 | # end 519 | # 520 | # def test_07_001 521 | # assert_equal( String, K.ann.w.class ) 522 | # end 523 | # 524 | # def test_07_002 525 | # assert_equal( 'key', K.ann.self.oid ) 526 | # end 527 | # 528 | # def test_07_003 529 | # #assert_equal( K.ann.self, K.ann(:self) ) 530 | # #assert_equal( K.ann(K), K.annotation ) 531 | # #assert_equal( K.ann.self.to_h, K.ann(K).to_h ) 532 | # #assert_equal( K.ann.self, K.ann(K) ) 533 | # end 534 | # end 535 | # 536 | 537 | # class TestAnnotation7 < Test::Unit::TestCase 538 | 539 | # class X 540 | # def x ; end 541 | # ann :x, :z=>1 542 | # end 543 | # class Y < X ; end 544 | # 545 | # def test_7_01 546 | # assert( Y.annotated?(:x) ) 547 | # end 548 | # def test_7_02 549 | # assert_equal( [:x1], Y.annotated_methods ) 550 | # end 551 | # def test_7_03 552 | # assert_equal( 1, Y.ann(:x).inheritance ) 553 | # assert_equal( 1, Y.ann(:x) ) 554 | # end 555 | # end 556 | 557 | 558 | =end 559 | --------------------------------------------------------------------------------