├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── shard.yml ├── spec ├── delimiter_tree_spec.cr └── spec_helper.cr └── src ├── delimiter ├── node.cr ├── result.cr ├── tree.cr └── version.cr └── delimiter_tree.cr /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/ 2 | /libs/ 3 | /.crystal/ 4 | /.shards/ 5 | 6 | 7 | # Libraries don't need dependency lock 8 | # Dependencies will be locked in application that uses them 9 | /shard.lock 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: crystal 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dru Jensen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Delimiter Tree 2 | 3 | A delimiter tree is a tree that is built from a string based on the delimiter. 4 | For example, if you have a string "foo;bar" then you will have a parent node 5 | "foo" and a child node "bar". Each node will hold a payload. 6 | 7 | The delimiter tree also supports two special characters. 8 | - : is used for a parameter 9 | - * is used to include a payload for all children nodes 10 | 11 | The tree will return an array of payloads for all matching * as well 12 | as the specific payload for the final matching string. 13 | 14 | 15 | 16 | ## Installation 17 | 18 | Add this to your application's `shard.yml`: 19 | 20 | ```yaml 21 | dependencies: 22 | delimiter_tree: 23 | github: drujensen/delimiter_tree 24 | ``` 25 | 26 | ## Usage 27 | 28 | The delimiter tree is used to return a payload or an array of payloads for a 29 | particular delimited string. This can be used for url paths or any situation 30 | where you need to specifically hold data per each segment of a delimited 31 | string. 32 | 33 | ```crystal 34 | require "delimiter_tree" 35 | 36 | tree = Delimiter::Tree(Symbol).new("/") 37 | tree.add "/*", :all_children 38 | tree.add "/products", :products 39 | tree.add "/products/:id", :specific_product 40 | 41 | result = tree.find "/products/2" 42 | 43 | puts result.payload 44 | # [:all_children, :products] 45 | 46 | puts result.params 47 | # :id => 2 48 | 49 | ``` 50 | 51 | ## Contributing 52 | 53 | 1. Fork it ( https://github.com/drujensen/delimiter_tree/fork ) 54 | 2. Create your feature branch (git checkout -b my-new-feature) 55 | 3. Commit your changes (git commit -am 'Add some feature') 56 | 4. Push to the branch (git push origin my-new-feature) 57 | 5. Create a new Pull Request 58 | 59 | ## Contributors 60 | 61 | - [drujensen](https://github.com/drujensen) Dru Jensen - creator, maintainer 62 | - [TechMagister](https://github.com/TechMagister) Arnaud Fernandés 63 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: delimiter_tree 2 | version: 0.1.4 3 | 4 | authors: 5 | - Dru Jensen 6 | 7 | license: MIT 8 | -------------------------------------------------------------------------------- /spec/delimiter_tree_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe Delimiter::Tree do 4 | 5 | it "builds a tree" do 6 | tree = Delimiter::Tree(Symbol).new 7 | tree.add "/", :root 8 | tree.find("/").found.should be_true 9 | end 10 | 11 | it "returns the payload" do 12 | tree = Delimiter::Tree(Symbol).new 13 | tree.add "/", :root 14 | tree.find("/").payload.should eq [:root] 15 | end 16 | 17 | it "returns an array of payload" do 18 | tree = Delimiter::Tree(Symbol).new 19 | tree.add "/", :root 20 | tree.add "/*", :all_children 21 | tree.add "/products", :products 22 | tree.add "/products/*", :all_products 23 | tree.add "/products/:id", :product 24 | tree.find("/products/2").payload.should eq [:all_children, :all_products, :product] 25 | end 26 | 27 | it "supports params" do 28 | tree = Delimiter::Tree(Symbol).new 29 | tree.add "/", :root 30 | tree.add "/:test", :test 31 | result = tree.find "/1234" 32 | result.params["test"].should eq "1234" 33 | end 34 | 35 | it "found? is false if not found" do 36 | tree = Delimiter::Tree(Symbol).new 37 | tree.add "/", :root 38 | tree.add "/products", :products 39 | tree.add "/products/:id", :product 40 | result = tree.find "/product" 41 | result.found?.should be_false 42 | end 43 | 44 | it "found? is true if found with *" do 45 | tree = Delimiter::Tree(Symbol).new 46 | tree.add "/*", :root 47 | result = tree.find "/1234" 48 | 49 | result.payload.should eq [:root] 50 | result.found?.should be_true 51 | end 52 | 53 | it "found? is true if found" do 54 | tree = Delimiter::Tree(Symbol).new 55 | tree.add "/", :root 56 | tree.add "/products", :products 57 | tree.add "/products/:id", :product 58 | result = tree.find "/products" 59 | result.found?.should be_true 60 | end 61 | 62 | it "should not duplicate payload" do 63 | tree = Delimiter::Tree(Symbol).new 64 | tree.add "/*", :all 65 | tree.find("/test/part/hop").payload.should eq [:all] # FAIL here 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/delimiter_tree" 3 | -------------------------------------------------------------------------------- /src/delimiter/node.cr: -------------------------------------------------------------------------------- 1 | module Delimiter 2 | class Node(T) 3 | getter key 4 | property payload 5 | property children 6 | 7 | def initialize(@key : String, @payload = [] of T) 8 | @children = {} of String => Node(T) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/delimiter/result.cr: -------------------------------------------------------------------------------- 1 | module Delimiter 2 | class Result(T) 3 | getter params 4 | property payload 5 | property found 6 | 7 | def initialize 8 | @params = {} of String => String 9 | @payload = [] of T 10 | @found = false 11 | end 12 | 13 | def found? 14 | @found 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /src/delimiter/tree.cr: -------------------------------------------------------------------------------- 1 | require "./node" 2 | require "./result" 3 | 4 | module Delimiter 5 | class Tree(T) 6 | property root : Node(T) 7 | property delimiter : String 8 | 9 | def initialize(@delimiter : String = "/") 10 | @root = Node(T).new("") 11 | end 12 | 13 | def add(path : String, payload : T) 14 | pos = @root 15 | parts = path.split(@delimiter) 16 | parts.each do |key| 17 | unless pos.children.has_key? key 18 | pos.children[key] = Node(T).new(key) 19 | end 20 | pos = pos.children[key] 21 | end 22 | pos.payload << payload 23 | end 24 | 25 | def find(path : String) 26 | result = Result(T).new 27 | 28 | pos = @root 29 | last_pos = pos 30 | 31 | parts = path.split(@delimiter) 32 | parts.each do |part| 33 | if pos.children.has_key?("*") 34 | pos.children["*"].payload.each {|p| result.payload << p} 35 | end 36 | 37 | if pos.children.has_key? part 38 | pos = pos.children[part] 39 | else 40 | pos.children.each_key do |key| 41 | if key.starts_with? ":" 42 | result.params[key.sub(":", "")] = part 43 | pos = pos.children[key] 44 | end 45 | end 46 | end 47 | 48 | pos == last_pos ? break : (last_pos = pos) 49 | end 50 | pos.payload.each {|p| result.payload << p} 51 | result.found = !result.payload.empty? 52 | result 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /src/delimiter/version.cr: -------------------------------------------------------------------------------- 1 | module DelimiterTree 2 | VERSION = "0.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /src/delimiter_tree.cr: -------------------------------------------------------------------------------- 1 | require "./delimiter/tree" 2 | 3 | --------------------------------------------------------------------------------