Skip to content

Commit

Permalink
Merge branch 'dev' + gemspec changes + changelog
Browse files Browse the repository at this point in the history
  • Loading branch information
pierrelegall committed Jan 14, 2018
2 parents 007fb9b + 9779bbc commit 63d801c
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 32 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# v2.0.2
- More documentation
- Better readme
- Better module naming
- Separation of Visitor and NoVisitMethodError tests

# v2.0.1
- Fix the `self` reference problem in arity zero visit methods
- Fix Visitor#visit_methods (when included)
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ You can find [here](https://en.wikipedia.org/wiki/Visitor_pattern) the well docu
## Benefits

- it **cross the ancestors list (classes and modules in the hierarchy)** to find an associated visit method (like overloading in some statically typed language)
- it **works without polluting visitable objects interface** with an `accept` method; consequently all objects are potentially visitable
- it allows class instance visitors (when `Eavi::Visitor` is included) and visitor modules (when `Eavi::Visitor` is extended), all-in-one module
- it comes with its own little internal Domain-Specific Language (see code examples below)
- it **works without polluting visitable objects interface** with an `accept` method; consequently all objects are visitable
- it allows visitors as class instances (when `Eavi::Visitor` is included) and visitors as modules (when `Eavi::Visitor` is extended), all-in-one gem
- it comes with an explicit API, let's call it a DSL (see code examples below)
- it raises a custom error (`Eavi::NoVisitMethodError`, a subtype of `TypeError`) if a visitor cannot handle an object

## How to use
Expand Down
6 changes: 3 additions & 3 deletions eavi.gemspec
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
Gem::Specification.new do |s|
s.name = 'eavi'
s.version = '2.0.1'
s.date = '2017-03-05'
s.version = '2.0.2'
s.date = '2018-01-14'

s.summary = 'A visitor pattern helper for Ruby.'
s.description = 'Make the visitor pattern accessible and flexible in Ruby.'
s.description = 'Make the visitor pattern very accessible in Ruby.'

s.homepage = 'http://github.com/lepieru/eavi'
s.files = Dir['lib/**/*', 'LICENSE']
Expand Down
6 changes: 3 additions & 3 deletions lib/eavi/visit_method_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ module VisitMethodHelper
TEMPLATE = 'visit[%s]'.freeze
REGEXP = /^visit\[(.*)\]$/

# Return a visit method name for the type +type+.
# Returns a visit method name for the type +type+.
def self.gen_name(type)
return TEMPLATE % type.name
end

# Return true if the +visit_method_name+ is a well formed
# Returns true if the +visit_method_name+ is a well formed
# visit method name, else false.
def self.match(visit_method_name)
return REGEXP.match(visit_method_name)
end

# Return the type matching a visit method.
# Returns the type matching a visit method.
def self.get_type(visit_method)
type_symbol = match(visit_method).captures[0]
return const_get(type_symbol)
Expand Down
36 changes: 23 additions & 13 deletions lib/eavi/visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ module Eavi
# to make it a dynamic visitor (see the OOP visitor pattern).
module Visitor
# Call the visit method associated with the type of +object+.
#
# @param [Object] object The object to visit
# @param [Object] *args The arguments passed to the called visit method
# @param [Class] as: The class which the visit method is attached
# @returns The result of the called visit method
def visit(object, *args, as: object.class)
as.ancestors.each do |type|
visit_method_name = VisitMethodHelper.gen_name(type)
Expand All @@ -17,33 +22,38 @@ def visit(object, *args, as: object.class)

class << self
def included(visitor)
visitor.extend(ModuleDSL)
visitor.extend(ModuleMethods)
visitor.extend(ModuleMethodsWhenIncluded)
visitor.extend(DSL)
visitor.extend(MethodsWhenIncludedAndExtended)
visitor.extend(MethodsWhenIncluded)
end

def extended(visitor)
visitor.extend(ModuleDSL)
visitor.extend(ModuleMethods)
visitor.extend(ModuleMethodsWhenExtended)
visitor.extend(DSL)
visitor.extend(MethodsWhenIncludedAndExtended)
visitor.extend(MethodsWhenExtended)
end
end

# Domain-Specific Language for the module/class
module ModuleDSL
# DSL methods
module DSL
# DSL method to add visit methods on types +types+.
#
# @param [Array<Class>] *types Types attached to the new visit method
# @param [Proc] block The content of the visit method
def def_visit(*types, &block)
add_visit_method(*types, &block)
end

# DSL method to remove visit methods on types +types+.
#
# @param [Array<Class>] *types Types attached to the removed visit method
def undef_visit(*types)
remove_visit_method(*types)
end
end

# Extends if included or extended
module ModuleMethods
module MethodsWhenIncludedAndExtended
# Alias the `visit` method.
def alias_visit_method(visit_method_alias)
specialized_alias_visit_method(visit_method_alias)
Expand Down Expand Up @@ -74,12 +84,12 @@ def reset_visit_methods
end
end

# Return a list of the visit method.
# Returns a list of the visit method.
def visit_methods
specialized_visit_methods
end

# Return a list of the types with a visit method.
# Returns a list of the types with a visit method.
def visitable_types
return visit_methods.collect do |visit_method|
VisitMethodHelper.get_type(visit_method)
Expand All @@ -88,7 +98,7 @@ def visitable_types
end

# Extends only when included
module ModuleMethodsWhenIncluded
module MethodsWhenIncluded
private

def specialized_alias_visit_method(visit_method_alias)
Expand All @@ -115,7 +125,7 @@ def specialized_visit_methods
end

# Extends only when extended
module ModuleMethodsWhenExtended
module MethodsWhenExtended
private

def specialized_alias_visit_method(visit_method_alias)
Expand Down
12 changes: 12 additions & 0 deletions test/no_visit_method_error_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
require 'minitest/autorun'

require_relative '../lib/eavi/no_visit_method_error'
require_relative 'fixtures'

describe Eavi::NoVisitMethodError do
it 'is catched as TypeError' do
assert_raises TypeError do
raise Eavi::NoVisitMethodError.new(Reader, 'string', String)
end
end
end
12 changes: 2 additions & 10 deletions test/visitor_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
assert_equal @nice_reader.visit(@nice_page), 'Reading'
end

it 'visit as something else if asked for' do
it 'visit as something else if asked explicitly' do
Reader.class_eval do
def_visit Page do
'As page'
Expand All @@ -66,10 +66,6 @@
assert_raises Eavi::NoVisitMethodError do
@reader.visit(@page, as: String)
end

assert_raises TypeError do
raise Eavi::NoVisitMethodError.new(nil, nil, nil)
end
end

it 'has self as the visitor instance in blocks (with first arg)' do
Expand Down Expand Up @@ -243,7 +239,7 @@
assert_equal NicePrinter.visit(@nice_page), 'Printing'
end

it 'visit as something else if asked for' do
it 'visit as something else if asked explicitly' do
Printer.class_eval do
def_visit Page do
'As page'
Expand All @@ -267,10 +263,6 @@
assert_raises Eavi::NoVisitMethodError do
Printer.visit(@page, as: String)
end

assert_raises TypeError do
raise Eavi::NoVisitMethodError.new(nil, nil, nil)
end
end

it 'has self as the singleton visitor in blocks (with first arg)' do
Expand Down

0 comments on commit 63d801c

Please sign in to comment.