Class: Syntropy::RoutingTree

Inherits:
Object
  • Object
show all
Defined in:
lib/syntropy/routing_tree.rb

Overview

The RoutingTree class implements a file-based routing tree with support for static files, markdown files, ruby modules, parametric routes, subtree routes, nested middleware and error handlers.

A RoutingTree instance takes the given directory (root_dir) and constructs a tree of route entries corresponding to the directory’s contents. Finally, it generates an optimized router proc, which is used by the application to return a route entry for each incoming HTTP request.

Once initialized, the routing tree is immutable. When running Syntropy in watch mode, whenever a file or directory is changed, added or deleted, a new routing tree will be constructed, and the old one will be discarded.

File-based routing in Syntropy follows some simple rules:

  • Static files (anything other than markdown files or dynamic Ruby modules) are routed to according to their location in the file tree.

  • Index files with ‘.md` or `.rb` extension handle requests to their immediate containing directory. For example, `/users/index.rb` will handle requests to `/users`.

  • Index files with a ‘+` suffix will also handle requests to anywhere in their subtree. For example, `/users/index+.rb` will also handle requests to `/users/foo/bar`.

  • Other markdown and module files will handle requests to their bare name (that is, without the extension.) Thus, ‘/users/foo.rb` will handle requests to `/users/foo`. A route with a `+` suffix will also handle requests to the route’s subtree. Thus, ‘/users/foo+.rb` will also handle requests to `/users/foo/bar`.

  • Parametric routes are implemented by enclosing the route name in square brackets. For example, ‘/processes//index.rb` will handle requests to `/posts/14` etc. Parametric route parts can also be expressed as files, e.g. `/processes//sources/.rb` will handle requests to `/posts/14/sources/42` etc. The values for placeholders are added to the incoming request. Here too, a `+` suffix causes the route to also handle requests to its subtree.

  • Directories and files whose names start with an underscore, e.g. ‘/_foo` or `/docs/_bar.rb` are skipped and will not be added to the routing tree. This allows you to prevent access through the HTTP server to protected or internal modules or files.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root_dir:, mount_path:, **env) ⇒ void

Initializes a new RoutingTree instance and computes the routing tree

Parameters:

  • root_dir (String)

    root directory of file tree

  • mount_path (String)

    base URL path



51
52
53
54
55
56
57
58
59
60
# File 'lib/syntropy/routing_tree.rb', line 51

def initialize(root_dir:, mount_path:, **env)
  @root_dir = root_dir
  @mount_path = mount_path
  @static_map = {}
  @dynamic_map = {}
  @env = env
  @root = compute_tree
  @static_map.freeze
  @dynamic_map.freeze
end

Instance Attribute Details

#dynamic_mapObject (readonly)

Returns the value of attribute dynamic_map.



44
45
46
# File 'lib/syntropy/routing_tree.rb', line 44

def dynamic_map
  @dynamic_map
end

#mount_pathObject (readonly)

Returns the value of attribute mount_path.



44
45
46
# File 'lib/syntropy/routing_tree.rb', line 44

def mount_path
  @mount_path
end

#rootObject (readonly)

Returns the value of attribute root.



44
45
46
# File 'lib/syntropy/routing_tree.rb', line 44

def root
  @root
end

#root_dirObject (readonly)

Returns the value of attribute root_dir.



44
45
46
# File 'lib/syntropy/routing_tree.rb', line 44

def root_dir
  @root_dir
end

#static_mapObject (readonly)

Returns the value of attribute static_map.



44
45
46
# File 'lib/syntropy/routing_tree.rb', line 44

def static_map
  @static_map
end

Instance Method Details

#compute_clean_url_path(fn) ⇒ String

Computes a “clean” URL path for the given path. Modules and markdown are stripped of their extensions, and index file paths are also converted to the containing directory path. For example, the clean URL path for ‘/foo/bar.rb` is `/foo/bar`. The Clean URL path for `/bar/baz/index.rb` is `/bar/baz`.

Parameters:

  • fn (String)

    file path

Returns:

  • (String)

    clean path



102
103
104
105
106
107
108
109
110
111
112
# File 'lib/syntropy/routing_tree.rb', line 102

def compute_clean_url_path(fn)
  rel_path = fn.sub(@root_dir, '')
  case rel_path
  when /^(.*)\/index\.(md|rb|html)$/
    Regexp.last_match(1).then { it == '' ? '/' : it }
  when /^(.*)\.(md|rb|html)$/
    Regexp.last_match(1)
  else
    rel_path
  end
end

#fn_to_rel_path(fn) ⇒ String

Converts filename to relative path.

Parameters:

  • fn (String)

    filename

Returns:

  • (String)

    relative path



118
119
120
# File 'lib/syntropy/routing_tree.rb', line 118

def fn_to_rel_path(fn)
  fn.sub(/^#{Regexp.escape(@root_dir)}\//, '').sub(/\.[^\.]+$/, '')
end

#route_error_handler(entry) ⇒ String?

Returns the first error handler found for the entry. If no error handler exists for the entry itself, the search continues up the tree through the entry’s ancestors.

Parameters:

  • entry (Hash)

    route entry

Returns:

  • (String, nil)

    filename of error handler, or nil if not found



75
76
77
78
79
# File 'lib/syntropy/routing_tree.rb', line 75

def route_error_handler(entry)
  return entry[:error] if entry[:error]

  entry[:parent] && route_error_handler(entry[:parent])
end

#route_hooks(entry) ⇒ Array<String>

Returns a list of all hooks found up the tree from the given entry. Hooks are returned ordered from the tree root down to the given entry.

Parameters:

  • entry (Hash)

    route entry

Returns:

  • (Array<String>)

    list of hook entries



86
87
88
89
90
91
92
93
# File 'lib/syntropy/routing_tree.rb', line 86

def route_hooks(entry)
  hooks = []
  while entry
    hooks.unshift(entry[:hook]) if entry[:hook]
    entry = entry[:parent]
  end
  hooks
end

#router_procProc

Returns the generated router proc for the routing tree

Returns:

  • (Proc)

    router proc



65
66
67
# File 'lib/syntropy/routing_tree.rb', line 65

def router_proc
  @router_proc ||= compile_router_proc
end