Introduce types for forward compiler compatiblity

After Crystal 0.15, compiler will require declare the types used
by instance variables on classes.

This require changes to the usage of `Radix::Tree` by introducing
the type of payload elements it will handle:

    # Will only support symbols as payload
    tree = Radix::Tree(Symbol).new
    tree.add "/", :root

    # Error: cannot add node with anything other than Symbol
    tree.add "/meaning-of-life", 42

The changes ensure future compatibility with Crystal and also
enforces a more declarative usage of `Radix::Tree`.

If necessary, you can combine multiple types to ensure a tree
can contain all the wide range of payloads you need:

    tree = Radix::Tree.new(Foo | Bar | Symbol).new
    tree.add "/", :root
    tree.add "/foo", foo_instance

This change includes:

- Tree, Node and Result has been updated to require types.
- Node is capable of have optional payload (from defined type).
- Documentation has been updated to reflect this change.
This commit is contained in:
Luis Lavena 2016-04-14 20:46:12 -03:00
parent 18bf9b132e
commit 9003075ec7
8 changed files with 190 additions and 109 deletions

View file

@ -4,6 +4,15 @@ All notable changes to Radix project will be documented in this file.
This project aims to comply with [Semantic Versioning](http://semver.org/), This project aims to comply with [Semantic Versioning](http://semver.org/),
so please check *Changed* and *Removed* notes before upgrading. so please check *Changed* and *Removed* notes before upgrading.
## [Unreleased]
### Fixed
- Improve forward compatibility with newer versions of the compiler by adding
missing types to solve type inference errors.
### Changed
- `Radix::Tree` now requires the usage of a type which will be used as node's
payload. See [README](README.md) for details.
## [0.2.1] - 2016-03-15 ## [0.2.1] - 2016-03-15
### Fixed ### Fixed
- Correct `Result#key` incorrect inferred type. - Correct `Result#key` incorrect inferred type.

View file

@ -1,11 +1,11 @@
# Radix Tree # Radix Tree
[![Build Status](https://travis-ci.org/luislavena/radix.svg?branch=master)](https://travis-ci.org/luislavena/radix)
[![docrystal.org](http://docrystal.org/badge.svg?style=round)](http://docrystal.org/github.com/luislavena/radix)
[Radix tree](https://en.wikipedia.org/wiki/Radix_tree) implementation for [Radix tree](https://en.wikipedia.org/wiki/Radix_tree) implementation for
Crystal language Crystal language
[![Build Status](https://travis-ci.org/luislavena/radix.svg?branch=master)](https://travis-ci.org/luislavena/radix)
[![docrystal.org](http://docrystal.org/badge.svg?style=round)](http://docrystal.org/github.com/luislavena/radix)
## Installation ## Installation
Add this to your application's `shard.yml`: Add this to your application's `shard.yml`:
@ -18,12 +18,14 @@ dependencies:
## Usage ## Usage
### Building Trees
You can associate a *payload* with each path added to the tree: You can associate a *payload* with each path added to the tree:
```crystal ```crystal
require "radix" require "radix"
tree = Radix::Tree.new tree = Radix::Tree(Symbol).new
tree.add "/products", :products tree.add "/products", :products
tree.add "/products/featured", :featured tree.add "/products/featured", :featured
@ -34,6 +36,30 @@ if result.found?
end end
``` ```
The types allowed for payload are defined on Tree definition:
```crystal
tree = Radix::Tree(Symbol).new
# Good, since Symbol is allowed as payload
tree.add "/", :root
# Compilation error, Int32 is not allowed
tree.add "/meaning-of-life", 42
```
Can combine multiple types if needed:
```crystal
tree = Radix::Tree(Int32 | String | Symbol).new
tree.add "/", :root
tree.add "/meaning-of-life", 42
tree.add "/hello", "world"
```
### Lookup and placeholders
You can also extract values from placeholders (as named segments or globbing): You can also extract values from placeholders (as named segments or globbing):
```crystal ```crystal
@ -53,8 +79,8 @@ Please see `Radix::Tree#add` documentation for more usage examples.
Pretty much all Radix implementations have their limitations and this project Pretty much all Radix implementations have their limitations and this project
is no exception. is no exception.
When designing and adding *paths* to build a Tree, please consider that two When designing and adding *paths* to a Tree, please consider that two different
different named parameters cannot share the same level: named parameters cannot share the same level:
```crystal ```crystal
tree.add "/", :root tree.add "/", :root

View file

@ -4,7 +4,7 @@ module Radix
describe Node do describe Node do
describe "#key=" do describe "#key=" do
it "accepts change of key after initialization" do it "accepts change of key after initialization" do
node = Node.new("abc") node = Node(Nil).new("abc")
node.key.should eq("abc") node.key.should eq("abc")
node.key = "xyz" node.key = "xyz"
@ -23,39 +23,45 @@ module Radix
node.payload.should eq(1_000) node.payload.should eq(1_000)
end end
# This example focuses on the internal representation of `payload`
# as inferred from supplied types and default values.
#
# We cannot compare `typeof` against `property!` since it excludes `Nil`
# from the possible types.
it "makes optional to provide a payload" do it "makes optional to provide a payload" do
node = Node.new("abc") node = Node(Int32).new("abc")
node.payload?.should be_falsey node.payload?.should be_falsey
typeof(node.@payload).should eq(Int32 | Nil)
end end
end end
describe "#priority" do describe "#priority" do
it "calculates it based on key size" do it "calculates it based on key size" do
node = Node.new("a") node = Node(Nil).new("a")
node.priority.should eq(1) node.priority.should eq(1)
node = Node.new("abc") node = Node(Nil).new("abc")
node.priority.should eq(3) node.priority.should eq(3)
end end
it "returns zero for catch all (globbed) key" do it "returns zero for catch all (globbed) key" do
node = Node.new("*filepath") node = Node(Nil).new("*filepath")
node.priority.should eq(0) node.priority.should eq(0)
node = Node.new("/src/*filepath") node = Node(Nil).new("/src/*filepath")
node.priority.should eq(0) node.priority.should eq(0)
end end
it "returns one for keys with named parameters" do it "returns one for keys with named parameters" do
node = Node.new(":query") node = Node(Nil).new(":query")
node.priority.should eq(1) node.priority.should eq(1)
node = Node.new("/search/:query") node = Node(Nil).new("/search/:query")
node.priority.should eq(1) node.priority.should eq(1)
end end
it "changes when key changes" do it "changes when key changes" do
node = Node.new("a") node = Node(Nil).new("a")
node.priority.should eq(1) node.priority.should eq(1)
node.key = "abc" node.key = "abc"
@ -71,10 +77,10 @@ module Radix
describe "#sort!" do describe "#sort!" do
it "orders children by priority" do it "orders children by priority" do
root = Node.new("/") root = Node(Int32).new("/")
node1 = Node.new("a") node1 = Node(Int32).new("a", 1)
node2 = Node.new("bc") node2 = Node(Int32).new("bc", 2)
node3 = Node.new("def") node3 = Node(Int32).new("def", 3)
root.children.push(node1, node2, node3) root.children.push(node1, node2, node3)
root.sort! root.sort!
@ -85,10 +91,10 @@ module Radix
end end
it "orders catch all and named parameters lower than others" do it "orders catch all and named parameters lower than others" do
root = Node.new("/") root = Node(Int32).new("/")
node1 = Node.new("*filepath") node1 = Node(Int32).new("*filepath", 1)
node2 = Node.new("abc") node2 = Node(Int32).new("abc", 2)
node3 = Node.new(":query") node3 = Node(Int32).new(":query", 3)
root.children.push(node1, node2, node3) root.children.push(node1, node2, node3)
root.sort! root.sort!

View file

@ -5,15 +5,15 @@ module Radix
describe "#found?" do describe "#found?" do
context "a new instance" do context "a new instance" do
it "returns false when no payload is associated" do it "returns false when no payload is associated" do
result = Result.new result = Result(Nil).new
result.found?.should be_false result.found?.should be_false
end end
end end
context "with a payload" do context "with a payload" do
it "returns true" do it "returns true" do
node = Node.new("/", :root) node = Node(Symbol).new("/", :root)
result = Result.new result = Result(Symbol).new
result.use node result.use node
result.found?.should be_true result.found?.should be_true
@ -24,15 +24,15 @@ module Radix
describe "#key" do describe "#key" do
context "a new instance" do context "a new instance" do
it "returns an empty key" do it "returns an empty key" do
result = Result.new result = Result(Nil).new
result.key.should eq("") result.key.should eq("")
end end
end end
context "given one used node" do context "given one used node" do
it "returns the node key" do it "returns the node key" do
node = Node.new("/", :root) node = Node(Symbol).new("/", :root)
result = Result.new result = Result(Symbol).new
result.use node result.use node
result.key.should eq("/") result.key.should eq("/")
@ -41,9 +41,9 @@ module Radix
context "using multiple nodes" do context "using multiple nodes" do
it "combines the node keys" do it "combines the node keys" do
node1 = Node.new("/", :root) node1 = Node(Symbol).new("/", :root)
node2 = Node.new("about", :about) node2 = Node(Symbol).new("about", :about)
result = Result.new result = Result(Symbol).new
result.use node1 result.use node1
result.use node2 result.use node2
@ -54,8 +54,8 @@ module Radix
describe "#use" do describe "#use" do
it "uses the node payload" do it "uses the node payload" do
node = Node.new("/", :root) node = Node(Symbol).new("/", :root)
result = Result.new result = Result(Symbol).new
result.payload?.should be_falsey result.payload?.should be_falsey
result.use node result.use node
@ -64,8 +64,8 @@ module Radix
end end
it "allow not to assign payload" do it "allow not to assign payload" do
node = Node.new("/", :root) node = Node(Symbol).new("/", :root)
result = Result.new result = Result(Symbol).new
result.payload?.should be_falsey result.payload?.should be_falsey
result.use node, payload: false result.use node, payload: false

View file

@ -3,7 +3,7 @@ require "../spec_helper"
# Silence deprecation warnings when running specs and allow # Silence deprecation warnings when running specs and allow
# capture them for inspection. # capture them for inspection.
module Radix module Radix
class Tree class Tree(T)
@show_deprecations = false @show_deprecations = false
@stderr : MemoryIO? @stderr : MemoryIO?
@ -20,12 +20,15 @@ module Radix
end end
end end
# Simple Payload class
record Payload
module Radix module Radix
describe Tree do describe Tree do
context "a new instance" do context "a new instance" do
it "contains a root placeholder node" do it "contains a root placeholder node" do
tree = Tree.new tree = Tree(Symbol).new
tree.root.should be_a(Node) tree.root.should be_a(Node(Symbol))
tree.root.payload?.should be_falsey tree.root.payload?.should be_falsey
tree.root.placeholder?.should be_true tree.root.placeholder?.should be_true
end end
@ -34,9 +37,9 @@ module Radix
describe "#add" do describe "#add" do
context "on a new instance" do context "on a new instance" do
it "replaces placeholder with new node" do it "replaces placeholder with new node" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/abc", :abc tree.add "/abc", :abc
tree.root.should be_a(Node) tree.root.should be_a(Node(Symbol))
tree.root.placeholder?.should be_false tree.root.placeholder?.should be_false
tree.root.payload?.should be_truthy tree.root.payload?.should be_truthy
tree.root.payload.should eq(:abc) tree.root.payload.should eq(:abc)
@ -45,7 +48,7 @@ module Radix
context "shared root" do context "shared root" do
it "inserts properly adjacent nodes" do it "inserts properly adjacent nodes" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/a", :a tree.add "/a", :a
tree.add "/bc", :bc tree.add "/bc", :bc
@ -61,7 +64,7 @@ module Radix
end end
it "inserts nodes with shared parent" do it "inserts nodes with shared parent" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/abc", :abc tree.add "/abc", :abc
tree.add "/axyz", :axyz tree.add "/axyz", :axyz
@ -78,7 +81,7 @@ module Radix
end end
it "inserts multiple parent nodes" do it "inserts multiple parent nodes" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/admin/users", :users tree.add "/admin/users", :users
tree.add "/admin/products", :products tree.add "/admin/products", :products
@ -107,7 +110,7 @@ module Radix
end end
it "inserts multiple nodes with mixed parents" do it "inserts multiple nodes with mixed parents" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/authorizations", :authorizations tree.add "/authorizations", :authorizations
tree.add "/authorizations/:id", :authorization tree.add "/authorizations/:id", :authorization
tree.add "/applications", :applications tree.add "/applications", :applications
@ -127,7 +130,7 @@ module Radix
end end
it "supports insertion of mixed routes out of order" do it "supports insertion of mixed routes out of order" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/user/repos", :my_repos tree.add "/user/repos", :my_repos
tree.add "/users/:user/repos", :user_repos tree.add "/users/:user/repos", :user_repos
tree.add "/users/:user", :user tree.add "/users/:user", :user
@ -148,9 +151,30 @@ module Radix
end end
end end
context "mixed payloads" do
it "allows node with different payloads" do
payload1 = Payload.new
payload2 = Payload.new
tree = Tree(Payload | Symbol).new
tree.add "/", :root
tree.add "/a", payload1
tree.add "/bc", payload2
# / (:root)
# +-bc (payload2)
# \-a (payload1)
tree.root.children.size.should eq(2)
tree.root.children[0].key.should eq("bc")
tree.root.children[0].payload.should eq(payload2)
tree.root.children[1].key.should eq("a")
tree.root.children[1].payload.should eq(payload1)
end
end
context "dealing with duplicates" do context "dealing with duplicates" do
it "does not allow same path be defined twice" do it "does not allow same path be defined twice" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/abc", :abc tree.add "/abc", :abc
@ -164,7 +188,7 @@ module Radix
context "dealing with catch all and named parameters" do context "dealing with catch all and named parameters" do
it "prioritizes nodes correctly" do it "prioritizes nodes correctly" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/*filepath", :all tree.add "/*filepath", :all
tree.add "/products", :products tree.add "/products", :products
@ -193,7 +217,7 @@ module Radix
end end
it "does not split named parameters across shared key" do it "does not split named parameters across shared key" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/:category", :category tree.add "/:category", :category
tree.add "/:category/:subcategory", :subcategory tree.add "/:category/:subcategory", :subcategory
@ -210,7 +234,7 @@ module Radix
end end
it "does not allow different named parameters sharing same level" do it "does not allow different named parameters sharing same level" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/:post", :post tree.add "/:post", :post
@ -224,7 +248,7 @@ module Radix
describe "#find" do describe "#find" do
context "a single node" do context "a single node" do
it "does not find when using different path" do it "does not find when using different path" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/about", :about tree.add "/about", :about
result = tree.find "/products" result = tree.find "/products"
@ -232,7 +256,7 @@ module Radix
end end
it "finds when using matching path" do it "finds when using matching path" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/about", :about tree.add "/about", :about
result = tree.find "/about" result = tree.find "/about"
@ -243,7 +267,7 @@ module Radix
end end
it "finds when using path with trailing slash" do it "finds when using path with trailing slash" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/about", :about tree.add "/about", :about
result = tree.find "/about/" result = tree.find "/about/"
@ -252,7 +276,7 @@ module Radix
end end
it "finds when key has trailing slash" do it "finds when key has trailing slash" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/about/", :about tree.add "/about/", :about
result = tree.find "/about" result = tree.find "/about"
@ -264,7 +288,7 @@ module Radix
context "nodes with shared parent" do context "nodes with shared parent" do
it "finds matching path" do it "finds matching path" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/abc", :abc tree.add "/abc", :abc
tree.add "/axyz", :axyz tree.add "/axyz", :axyz
@ -276,7 +300,7 @@ module Radix
end end
it "finds matching path across parents" do it "finds matching path across parents" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/admin/users", :users tree.add "/admin/users", :users
tree.add "/admin/products", :products tree.add "/admin/products", :products
@ -292,7 +316,7 @@ module Radix
context "dealing with catch all" do context "dealing with catch all" do
it "finds matching path" do it "finds matching path" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/*filepath", :all tree.add "/*filepath", :all
tree.add "/about", :about tree.add "/about", :about
@ -304,7 +328,7 @@ module Radix
end end
it "returns catch all in parameters" do it "returns catch all in parameters" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/*filepath", :all tree.add "/*filepath", :all
tree.add "/about", :about tree.add "/about", :about
@ -316,7 +340,7 @@ module Radix
end end
it "returns optional catch all" do it "returns optional catch all" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/search/*extra", :extra tree.add "/search/*extra", :extra
@ -328,7 +352,7 @@ module Radix
end end
it "does not find when catch all is not full match" do it "does not find when catch all is not full match" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/search/public/*query", :search tree.add "/search/public/*query", :search
@ -339,7 +363,7 @@ module Radix
context "dealing with named parameters" do context "dealing with named parameters" do
it "finds matching path" do it "finds matching path" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/products", :products tree.add "/products", :products
tree.add "/products/:id", :product tree.add "/products/:id", :product
@ -352,7 +376,7 @@ module Radix
end end
it "does not find partial matching path" do it "does not find partial matching path" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/products", :products tree.add "/products", :products
tree.add "/products/:id/edit", :edit tree.add "/products/:id/edit", :edit
@ -362,7 +386,7 @@ module Radix
end end
it "returns named parameters in result" do it "returns named parameters in result" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/products", :products tree.add "/products", :products
tree.add "/products/:id", :product tree.add "/products/:id", :product
@ -375,7 +399,7 @@ module Radix
end end
it "returns unicode values in parameters" do it "returns unicode values in parameters" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/language/:name", :language tree.add "/language/:name", :language
tree.add "/language/:name/about", :about tree.add "/language/:name/about", :about
@ -389,7 +413,7 @@ module Radix
context "dealing with multiple named parameters" do context "dealing with multiple named parameters" do
it "finds matching path" do it "finds matching path" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/:section/:page", :static_page tree.add "/:section/:page", :static_page
@ -400,7 +424,7 @@ module Radix
end end
it "returns named parameters in result" do it "returns named parameters in result" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/:section/:page", :static_page tree.add "/:section/:page", :static_page
@ -417,7 +441,7 @@ module Radix
context "dealing with both catch all and named parameters" do context "dealing with both catch all and named parameters" do
it "finds matching path" do it "finds matching path" do
tree = Tree.new tree = Tree(Symbol).new
tree.add "/", :root tree.add "/", :root
tree.add "/*filepath", :all tree.add "/*filepath", :all
tree.add "/products", :products tree.add "/products", :products

View file

@ -26,11 +26,11 @@ module Radix
# node.children.map &.priority # node.children.map &.priority
# # => [3, 2, 1] # # => [3, 2, 1]
# ``` # ```
class Node class Node(T)
getter key : String getter key
getter? placeholder : Bool getter? placeholder
property! payload property! payload : T | Nil
property children : Array(Node) property children
# Returns the priority of the Node based on it's *key* # Returns the priority of the Node based on it's *key*
# #
@ -42,16 +42,16 @@ module Radix
# * Any other type of key will receive priority based on its size. # * Any other type of key will receive priority based on its size.
# #
# ``` # ```
# Node.new("a").priority # Node(Nil).new("a").priority
# # => 1 # # => 1
# #
# Node.new("abc").priority # Node(Nil).new("abc").priority
# # => 3 # # => 3
# #
# Node.new("*filepath").priority # Node(Nil).new("*filepath").priority
# # => 0 # # => 0
# #
# Node.new(":query").priority # Node(Nil).new(":query").priority
# # => 1 # # => 1
# ``` # ```
getter priority : Int32 getter priority : Int32
@ -59,16 +59,30 @@ module Radix
# Instantiate a Node # Instantiate a Node
# #
# - *key* - A `String` that represents this node. # - *key* - A `String` that represents this node.
# - *payload* - An Optional payload for this node. # - *payload* - An optional payload for this node.
def initialize(@key : String, @payload = nil, @placeholder = false) #
@children = [] of Node # When *payload* is not supplied, ensure the type of the node is provided
# instead:
#
# ```
# # Good, node type is inferred from payload (Symbol)
# node = Node.new("/", :root)
#
# # Good, node type is now Int32 but payload is optional
# node = Node(Int32).new("/")
#
# # Error, node type cannot be inferred (compiler error)
# node = Node.new("/")
# ```
def initialize(@key : String, @payload : T? = nil, @placeholder = false)
@children = [] of Node(T)
@priority = compute_priority @priority = compute_priority
end end
# Changes current *key* # Changes current *key*
# #
# ``` # ```
# node = Node.new("a") # node = Node(Nil).new("a")
# node.key # node.key
# # => "a" # # => "a"
# #
@ -80,7 +94,7 @@ module Radix
# This will also result in a new priority for the node. # This will also result in a new priority for the node.
# #
# ``` # ```
# node = Node.new("a") # node = Node(Nil).new("a")
# node.priority # node.priority
# # => 1 # # => 1
# #
@ -88,7 +102,7 @@ module Radix
# node.priority # node.priority
# # => 6 # # => 6
# ``` # ```
def key=(@key : String) def key=(@key)
@priority = compute_priority @priority = compute_priority
end end
@ -115,11 +129,11 @@ module Radix
# This ensures highest priority nodes are listed before others. # This ensures highest priority nodes are listed before others.
# #
# ``` # ```
# root = Node.new("/") # root = Node(Nil).new("/")
# root.children << Node.new("*filepath") # node.priority => 0 # root.children << Node(Nil).new("*filepath") # node.priority => 0
# root.children << Node.new(":query") # node.priority => 1 # root.children << Node(Nil).new(":query") # node.priority => 1
# root.children << Node.new("a") # node.priority => 1 # root.children << Node(Nil).new("a") # node.priority => 1
# root.children << Node.new("bc") # node.priority => 2 # root.children << Node(Nil).new("bc") # node.priority => 2
# root.sort! # root.sort!
# #
# root.children.map &.priority # root.children.map &.priority

View file

@ -12,13 +12,15 @@ module Radix
# #
# A Result is also used recursively by `Tree#find` when collecting extra # A Result is also used recursively by `Tree#find` when collecting extra
# information like *params*. # information like *params*.
class Result class Result(T)
@key : String?
getter params getter params
getter! payload getter! payload : T?
# :nodoc: # :nodoc:
def initialize def initialize
@nodes = [] of Node @nodes = [] of Node(T)
@params = {} of String => String @params = {} of String => String
end end
@ -26,26 +28,26 @@ module Radix
# the result. # the result.
# #
# ``` # ```
# result = Result.new # result = Result(Symbol).new
# result.found? # result.found?
# # => false # # => false
# #
# root = Node.new("/", :root) # root = Node(Symbol).new("/", :root)
# result.use(root) # result.use(root)
# result.found? # result.found?
# # => true # # => true
# ``` # ```
def found? : Bool def found?
payload? ? true : false payload? ? true : false
end end
# Returns a String built based on the nodes used in the result # Returns a String built based on the nodes used in the result
# #
# ``` # ```
# node1 = Node.new("/", :root) # node1 = Node(Symbol).new("/", :root)
# node2 = Node.new("about", :about) # node2 = Node(Symbol).new("about", :about)
# #
# result = Result.new # result = Result(Symbol).new
# result.use node1 # result.use node1
# result.use node2 # result.use node2
# #
@ -56,11 +58,11 @@ module Radix
# When no node has been used, returns an empty String. # When no node has been used, returns an empty String.
# #
# ``` # ```
# result = Result.new # result = Result(Nil).new
# result.key # result.key
# # => "" # # => ""
# ``` # ```
def key : String def key
@key ||= begin @key ||= begin
String.build { |io| String.build { |io|
@nodes.each do |node| @nodes.each do |node|
@ -74,7 +76,7 @@ module Radix
# #
# * Collect `Node` for future references. # * Collect `Node` for future references.
# * Use *payload* if present. # * Use *payload* if present.
def use(node : Node, payload = true) def use(node : Node(T), payload = true)
# collect nodes # collect nodes
@nodes << node @nodes << node

View file

@ -12,7 +12,7 @@ module Radix
# #
# You can associate a *payload* at insertion which will be return back # You can associate a *payload* at insertion which will be return back
# at retrieval time. # at retrieval time.
class Tree class Tree(T)
# :nodoc: # :nodoc:
class DuplicateError < Exception class DuplicateError < Exception
def initialize(path) def initialize(path)
@ -30,10 +30,10 @@ module Radix
# Returns the root `Node` element of the Tree. # Returns the root `Node` element of the Tree.
# #
# On a new tree instance, this will be a placeholder. # On a new tree instance, this will be a placeholder.
getter root : Node getter root : Node(T)
def initialize def initialize
@root = Node.new("", placeholder: true) @root = Node(T).new("", placeholder: true)
end end
# Inserts given *path* into the Tree # Inserts given *path* into the Tree
@ -98,19 +98,19 @@ module Radix
# # \-*filepath (:all) # # \-*filepath (:all)
# tree.add "/about", :about # tree.add "/about", :about
# ``` # ```
def add(path : String, payload) def add(path : String, payload : T)
root = @root root = @root
# replace placeholder with new node # replace placeholder with new node
if root.placeholder? if root.placeholder?
@root = Node.new(path, payload) @root = Node(T).new(path, payload)
else else
add path, payload, root add path, payload, root
end end
end end
# :nodoc: # :nodoc:
private def add(path : String, payload, node : Node) private def add(path : String, payload : T, node : Node(T))
key_reader = Char::Reader.new(node.key) key_reader = Char::Reader.new(node.key)
path_reader = Char::Reader.new(path) path_reader = Char::Reader.new(path)
@ -151,7 +151,7 @@ module Radix
# if no existing child shared part of the key, add a new one # if no existing child shared part of the key, add a new one
unless added unless added
node.children << Node.new(new_key, payload) node.children << Node(T).new(new_key, payload)
end end
# adjust priorities # adjust priorities
@ -174,7 +174,7 @@ module Radix
new_key = node.key.byte_slice(path_reader.pos) new_key = node.key.byte_slice(path_reader.pos)
swap_payload = node.payload? ? node.payload : nil swap_payload = node.payload? ? node.payload : nil
new_node = Node.new(new_key, swap_payload) new_node = Node(T).new(new_key, swap_payload)
new_node.children.replace(node.children) new_node.children.replace(node.children)
# clear payload and children (this is no longer and endpoint) # clear payload and children (this is no longer and endpoint)
@ -189,7 +189,7 @@ module Radix
# determine if path still continues # determine if path still continues
if path_reader.pos < path.size if path_reader.pos < path.size
new_key = path.byte_slice(path_reader.pos) new_key = path.byte_slice(path_reader.pos)
node.children << Node.new(new_key, payload) node.children << Node(T).new(new_key, payload)
node.sort! node.sort!
# clear payload (no endpoint) # clear payload (no endpoint)
@ -223,7 +223,7 @@ module Radix
# # => :about # # => :about
# ``` # ```
def find(path : String) def find(path : String)
result = Result.new result = Result(T).new
root = @root root = @root
# walk the tree from root (first time) # walk the tree from root (first time)