From: Juha Pohjalainen Date: 2001-05-30T06:50:19+09:00 Subject: [ruby-talk:15963] navigator.rb - tree navigation --FCuugMFkClbJLl1L Content-Type: text/plain; charset=us-ascii Hi again! Here is one silly idea for tree-like data traversal with Ruby. Following three files (if attachments works) are: - navigator.rb - the main file and implementation - garden.rb - silly test environment - NavigatorTests.rb - Lapidary unit tests for navigator.rb Test environment contains following hierarchy: Earth -> Garden -> Tree -> Branch -> Leaf ( -> and bugs ) Idea is this. If we have handle for one leaf, we could use that leaf to find out all leaves in every garden on earth using following Ruby command: leaves = leaf.earthGardensTreesBranchesLeaves leaves.each { |found| puts found.to_s } [Or filter what ever intrests us!] Or if gardens have weather accessors, we could change weather in every garden, just using one leaf: leaf.gardensWeather = 'Sunny' -Jippo In our series of silly examples, we just saw episode: silly examples. -- __________________________________________________________________ [ pub 1024/951AFAF5 1995/12/12 Juha Pohjalainen ] [ fingerprint 41 56 7F F9 8E EC 16 35 BB 42 EF A7 DF 19 FA 31 ] [__ http://www.iki.fi/jmp/ ________________ GSM +358 40 570 1179 __] --FCuugMFkClbJLl1L Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="navigator.rb" #!/usr/local/bin/ruby module Navigator attr_accessor :container def tryturn value yield value if value end def breakMethodName name parts = name.to_s.split /([A-Z]{1}[a-z0-9=]+)/ parts.collect! { |item| item.downcase if not item.empty? } return parts.compact end def shortname allParts = type.to_s.split ':' return allParts[-1].downcase end def localHandler target, name, parameters return nil if not target or not name return target.method( name ).call( *parameters ).to_a if target.respond_to? name return target.to_a if target.respond_to? :shortname and name == target.shortname end def containerFinder target, name, parameters return( localHandler( target, name, parameters ) || ( containerFinder target.container, name, parameters if target.respond_to? :container ) ) end def commonFinder group, workingResult = [ ] results = [ ] group.each do |currentTarget| workingResult = yield workingResult, currentTarget workingResult.to_a.each { |item| results.push item if item } end return results end def partFinder targets, name, parameters return commonFinder( targets ) { |ignored, target| localHandler target, name, parameters } end def handleFirstPart name, parameters tryturn( containerFinder self, name, parameters ) {|t| return t} throw :rollback end def handleMiddleParts targets, parts return targets if not parts or parts.empty? return commonFinder( parts, targets ) { |result, target| partFinder result, target, nil } end def method_missing methodName, *parameters catch :rollback do parts = breakMethodName methodName return handleFirstPart parts.shift, parameters if parts.length == 1 targets = handleFirstPart parts.shift, nil lastPart = parts.pop targets = handleMiddleParts targets, parts return partFinder targets, lastPart, parameters end raise NameError, "can not navigate '#{methodName}' for '#{self}'" end end --FCuugMFkClbJLl1L Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="garden.rb" #!/usr/local/bin/ruby require 'navigator' module BigGarden class Earth include Navigator attr_reader :gardens def echo text ucase = text.upcase return "ECHO: '#{text}' #{text} #{ucase}" end def initialize @gardens = Array.new end def addGarden target @gardens.push target end end class Garden include Navigator attr_reader :trees attr_accessor :name def initialize( earth, name ) @trees = Array.new earth.addGarden self @container = earth @name = name end def addTree target @trees.push target end def worms one, two, three "Here we are at #{@name}! Worms #{one}, #{two} and little #{three}." end end class Tree include Navigator attr_reader :branches def initialize( garden ) @branches = Array.new garden.addTree self @container = garden end def addBranch target @branches.push target end end class Branch include Navigator attr_reader :leaves def initialize( tree ) @leaves = Array.new tree.addBranch self self.container = tree end def addLeaf target @leaves.push target end end class Leaf attr_accessor :bugs include Navigator def initialize( branch ) branch.addLeaf self self.container = branch end end end --FCuugMFkClbJLl1L Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="NavigatorTests.rb" #!/usr/local/bin/ruby require 'Lapidary/TestCase' require 'Lapidary/UI/Console/TestRunner' require 'garden' class NavigatorTests < Lapidary::TestCase def setup @earth = BigGarden::Earth.new # first anonymous garden garden = BigGarden::Garden.new @earth, 'First' tree = BigGarden::Tree.new garden branch = BigGarden::Branch.new tree BigGarden::Leaf.new branch BigGarden::Leaf.new branch BigGarden::Leaf.new branch # second garden, which is known @garden = BigGarden::Garden.new @earth, 'Second' @tree = BigGarden::Tree.new @garden @branch = BigGarden::Branch.new @tree @leaf = BigGarden::Leaf.new @branch @targets = [ @earth, @garden, @tree, @branch, @leaf ] end def testNotFound assertRaises( NameError, 'dummies' ) { @leaf.dummies } assertRaises( NameError, 'dummiesFound' ) { @leaf.dummiesFound } assertRaises( NameError, 'dummiesMummiesFound' ) { @leaf.dummiesMummiesFound } end # this is a very silly test, but possible, but do not do this, PLEASE def testSillyTest assertEqual 8, @earth.gardensGardenGardenGardenGardenName.length end # with this test, you can change names of all gardens def testNameChange assertEqual ['First','Second'], @earth.gardensName @leaf.earthGardensName = 'Ruined' assertEqual ['Ruined','Ruined'], @earth.gardensName end # with this test, all bugs are set to 0 at all leaves around the earth def testBugs assertEqual [], @earth.earthGardensTreesBranchesLeavesBugs @earth.earthGardensTreesBranchesLeavesBugs = 0 assertEqual [0,0,0,0], @earth.earthGardensTreesBranchesLeavesBugs @leaf.bugs = 3 assertEqual [0,0,0,3], @earth.earthGardensTreesBranchesLeavesBugs end # from any point of chain, earth echo can be found def testEchosAnyone @targets.each do |who| assertEqual ["ECHO: 'piip' piip PIIP"], who.earthEcho 'piip' end end # those worms are taking pictures of themselves, when they travel def testWorms expects = [ 'Here we are at First! Worms Joe, Moe and little Pike.', 'Here we are at Second! Worms Joe, Moe and little Pike.' ] assertEqual expects, @leaf.earthGardensWorms 'Joe', 'Moe', 'Pike' end # in this garden, there is only one tree def testGardenTrees assertEqual [@tree], @leaf.gardenTrees end # but all gardens have one more, and both found thru one leaf def testGardensTrees assertEqual 2, @leaf.gardensTrees.length end # two ways to find that branch def testGardensTreesBranches assertEqual [@branch], @leaf.gardenTreesBranches assertEqual [@branch], @leaf.treesBranches end # can we count all those leaves thru one leaf? def testLeaves assertEqual 4, @leaf.earthGardensTreesBranchesLeaves.length end # does a leaf know names of all gardens? def testGardensName assertEqual ['First','Second'], @leaf.earthGardensName end # does a leaf know all gardens? def testGarden assertEqual 2, @leaf.gardens.length end end Lapidary::UI::Console::TestRunner.run(NavigatorTests) --FCuugMFkClbJLl1L--