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/[proc_id]/index.rb will handle requests to /posts/14 etc. Parametric route parts can also be expressed as files, e.g. /processes/[id]/sources/[src_id].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
# 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
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



74
75
76
77
78
79
80
81
82
83
84
# File 'lib/syntropy/routing_tree.rb', line 74

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



90
91
92
# File 'lib/syntropy/routing_tree.rb', line 90

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

#mount_applet(path, applet) ⇒ void

This method returns an undefined value.

Mounts the given applet on the routng tree at the given (absolute) mount path. This method must be called before the router proc is generated.

Parameters:

  • path (String)

    absolute mount path for the applet

  • applet (Syntropy::App, Proc)

    applet



100
101
102
103
# File 'lib/syntropy/routing_tree.rb', line 100

def mount_applet(path, applet)
  path = rel_mount_path(path)
  mount_applet_on_tree(@root, path, applet)
end

#router_procProc

Returns the generated router proc for the routing tree

Returns:

  • (Proc)

    router proc



63
64
65
# File 'lib/syntropy/routing_tree.rb', line 63

def router_proc
  @router_proc ||= generate_router_proc
end