sansom 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/sansom/pine/matcher.cpp +79 -0
- data/ext/sansom/pine/pattern.cpp +134 -0
- data/ext/sansom/pine/pattern.h +56 -0
- data/lib/sansom/sansomable.rb +1 -1
- metadata +5 -8
- data/.gitignore +0 -23
- data/Gemfile +0 -4
- data/LICENSE.txt +0 -22
- data/README.md +0 -232
- data/changelog.md +0 -76
- data/sansom.gemspec +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f29764ffc96d04c4d04a32f6205e312501003245
|
4
|
+
data.tar.gz: bcc0fee0d61526e6d1f24eacf92af8aa16c182b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a6e16f0fcc5e37418367b4e67cfd135d1d62a5aabda0ec7bd2866ad9e712dafe06c826eb8e8b921f3d648c2823d9055d5f3b1ce7a5e9b57031dd67538269351e
|
7
|
+
data.tar.gz: a011da7c29be9334651a240ec0a381825b3766ba4a3cab35659c1d83b1129f36747267f1af14da9e96701c69c7d5ded2743b1cb3f9151086c3d2b890def70035
|
@@ -0,0 +1,79 @@
|
|
1
|
+
#include <iostream>
|
2
|
+
#include "pattern.h"
|
3
|
+
#include "ruby.h" // ruby header
|
4
|
+
|
5
|
+
using namespace std;
|
6
|
+
|
7
|
+
extern "C" void Init_matcher();
|
8
|
+
static lpm::pattern * getPattern(VALUE self);
|
9
|
+
static void matcher_gc_free(lpm::pattern *p);
|
10
|
+
static VALUE matcher_allocate(VALUE klass);
|
11
|
+
static VALUE matcher_initialize(VALUE self, VALUE rb_pattern);
|
12
|
+
static VALUE matcher_matches(VALUE self, VALUE rb_str);
|
13
|
+
static VALUE matcher_is_dynamic(VALUE self);
|
14
|
+
static VALUE matcher_splats(VALUE self, VALUE rb_str);
|
15
|
+
static VALUE matcher_mappings(VALUE self, VALUE rb_str);
|
16
|
+
|
17
|
+
VALUE rb_cPine = Qnil;
|
18
|
+
VALUE p_cMatcher = Qnil;
|
19
|
+
|
20
|
+
void Init_matcher() {
|
21
|
+
rb_cPine = rb_define_class("Pine", rb_cObject);
|
22
|
+
p_cMatcher = rb_define_class_under(rb_cPine, "Matcher", rb_cObject);
|
23
|
+
rb_define_alloc_func(p_cMatcher, matcher_allocate);
|
24
|
+
rb_define_method(p_cMatcher, "initialize", RUBY_METHOD_FUNC(matcher_initialize), 1);
|
25
|
+
rb_define_method(p_cMatcher, "matches?", RUBY_METHOD_FUNC(matcher_matches), 1);
|
26
|
+
rb_define_method(p_cMatcher, "dynamic?", RUBY_METHOD_FUNC(matcher_is_dynamic), 0);
|
27
|
+
rb_define_method(p_cMatcher, "splats", RUBY_METHOD_FUNC(matcher_splats), 1);
|
28
|
+
rb_define_method(p_cMatcher, "mappings", RUBY_METHOD_FUNC(matcher_mappings), 1);
|
29
|
+
}
|
30
|
+
|
31
|
+
static lpm::pattern * getPattern(VALUE self) {
|
32
|
+
lpm::pattern *p;
|
33
|
+
Data_Get_Struct(self, lpm::pattern, p);
|
34
|
+
return p;
|
35
|
+
}
|
36
|
+
|
37
|
+
static void matcher_gc_free(lpm::pattern *p) {
|
38
|
+
if (p) delete p;
|
39
|
+
p = NULL;
|
40
|
+
ruby_xfree(p);
|
41
|
+
}
|
42
|
+
|
43
|
+
static VALUE matcher_allocate(VALUE klass) {
|
44
|
+
return Data_Wrap_Struct(klass, NULL, matcher_gc_free, ruby_xmalloc(sizeof(lpm::pattern)));
|
45
|
+
}
|
46
|
+
|
47
|
+
static VALUE matcher_initialize(VALUE self, VALUE rb_pattern) {
|
48
|
+
lpm::pattern *p = getPattern(self);
|
49
|
+
new (p) lpm::pattern(StringValueCStr(rb_pattern));
|
50
|
+
return Qnil;
|
51
|
+
}
|
52
|
+
|
53
|
+
static VALUE matcher_matches(VALUE self, VALUE rb_str) {
|
54
|
+
return *(getPattern(self)) == string(StringValueCStr(rb_str)) ? Qtrue : Qfalse;
|
55
|
+
}
|
56
|
+
|
57
|
+
static VALUE matcher_is_dynamic(VALUE self) {
|
58
|
+
return getPattern(self)->is_dynamic() ? Qtrue : Qfalse;
|
59
|
+
}
|
60
|
+
|
61
|
+
static VALUE matcher_splats(VALUE self, VALUE rb_str) {
|
62
|
+
lpm::pattern *p = getPattern(self);
|
63
|
+
list<string> splats = p->extract_splats(StringValueCStr(rb_str));
|
64
|
+
VALUE ary = rb_ary_new2(splats.size());
|
65
|
+
for (list<string>::iterator i = splats.begin(); i != splats.end(); i++) {
|
66
|
+
rb_ary_push(ary, rb_str_new2((*i).c_str()));;
|
67
|
+
}
|
68
|
+
return ary;
|
69
|
+
}
|
70
|
+
|
71
|
+
static VALUE matcher_mappings(VALUE self, VALUE rb_str) {
|
72
|
+
lpm::pattern *p = getPattern(self);
|
73
|
+
map<string, string> mappings = p->extract_mappings(StringValueCStr(rb_str));
|
74
|
+
VALUE hsh = rb_hash_new();
|
75
|
+
for (map<string, string>::iterator i = mappings.begin(); i != mappings.end(); i++) {
|
76
|
+
rb_hash_aset(hsh, rb_str_new2(i->first.c_str()),rb_str_new2(i->second.c_str()));
|
77
|
+
}
|
78
|
+
return hsh;
|
79
|
+
}
|
@@ -0,0 +1,134 @@
|
|
1
|
+
#include "pattern.h"
|
2
|
+
|
3
|
+
namespace lpm {
|
4
|
+
bool pattern::operator==(const string &rhs) const { return matches(rhs); }
|
5
|
+
bool pattern::operator==(const pattern &rhs) const { return rhs._pattern == _pattern; }
|
6
|
+
bool pattern::operator!=(const string &rhs) const { return !(*this == rhs); }
|
7
|
+
|
8
|
+
bool pattern::is_dynamic() const { return (_index.size() > 0); }
|
9
|
+
string pattern::pattern_str() const { return _pattern; }
|
10
|
+
|
11
|
+
bool pattern::matches(string cppstr) const {
|
12
|
+
if (_pattern[0] == ':') return true;
|
13
|
+
if (_pattern == cppstr) return true;
|
14
|
+
|
15
|
+
char *pattern = (char *)_pattern.c_str();
|
16
|
+
char *str = (char *)cppstr.c_str();
|
17
|
+
int index_delta = 0;
|
18
|
+
|
19
|
+
for (auto i = _index.begin(); i != _index.end(); i++) {
|
20
|
+
unsigned idx = i->first;
|
21
|
+
string token = i->second;
|
22
|
+
|
23
|
+
char *vptr = str+idx+index_delta;
|
24
|
+
char *succ = pattern+idx+token.length();
|
25
|
+
unsigned succlen = (i == _index.end()) ? 0 : next(i)->first-(idx+token.length());
|
26
|
+
unsigned vlen = _advance_to_str(vptr, succ, succlen);
|
27
|
+
if (strncmp(succ, vptr, succlen) != 0) return false;
|
28
|
+
|
29
|
+
index_delta += vlen;
|
30
|
+
index_delta -= token.length();
|
31
|
+
}
|
32
|
+
|
33
|
+
return true;
|
34
|
+
}
|
35
|
+
|
36
|
+
map<string, string> pattern::extract_mappings(string cppstr) const {
|
37
|
+
map<string,string> m;
|
38
|
+
char *pattern = (char *)_pattern.c_str();
|
39
|
+
char *str = (char *)cppstr.c_str();
|
40
|
+
int index_delta = 0;
|
41
|
+
|
42
|
+
if (_pattern[0] == ':') {
|
43
|
+
m[_pattern.substr(1,_pattern.length()-1)] = cppstr;
|
44
|
+
return m;
|
45
|
+
}
|
46
|
+
|
47
|
+
for (auto i = _index.begin(); i != _index.end(); i++) {
|
48
|
+
unsigned idx = i->first;
|
49
|
+
string token = i->second;
|
50
|
+
|
51
|
+
char *vptr = str+idx+index_delta;
|
52
|
+
char *succ = pattern+idx+token.length();
|
53
|
+
unsigned succlen = (i == _index.end()) ? 0 : next(i)->first-(idx+token.length());
|
54
|
+
string value(vptr, _advance_to_str(vptr, succ, succlen));
|
55
|
+
|
56
|
+
index_delta += value.length();
|
57
|
+
index_delta -= token.length();
|
58
|
+
|
59
|
+
if (token[0] == '<' && token[token.length()-1] == '>') {
|
60
|
+
m[token.substr(1, token.length()-2)] = value;
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
return m;
|
65
|
+
}
|
66
|
+
|
67
|
+
list<string> pattern::extract_splats(string cppstr) const {
|
68
|
+
list<string> splats;
|
69
|
+
char *pattern = (char *)_pattern.c_str();
|
70
|
+
char *str = (char *)cppstr.c_str();
|
71
|
+
int index_delta = 0;
|
72
|
+
|
73
|
+
for (auto i = _index.begin(); i != _index.end(); i++) {
|
74
|
+
unsigned idx = i->first;
|
75
|
+
string token = i->second;
|
76
|
+
|
77
|
+
char *vptr = str+idx+index_delta;
|
78
|
+
char *succ = pattern+idx+token.length();
|
79
|
+
unsigned succlen = (i == _index.end()) ? 0 : next(i)->first-(idx+token.length());
|
80
|
+
string value(vptr, _advance_to_str(vptr, succ, succlen));
|
81
|
+
|
82
|
+
index_delta += value.length();
|
83
|
+
index_delta -= token.length();
|
84
|
+
|
85
|
+
if (token[0] == '*') splats.push_back(value);
|
86
|
+
}
|
87
|
+
return splats;
|
88
|
+
}
|
89
|
+
|
90
|
+
void pattern::create(string ptrn) {
|
91
|
+
_pattern = ptrn;
|
92
|
+
char *cptrn = (char *)_pattern.c_str();
|
93
|
+
_index = _gen_indeces(cptrn,cptrn);
|
94
|
+
}
|
95
|
+
|
96
|
+
unsigned pattern::_advance_to_str(char *&ptr, char *str, unsigned n) const {
|
97
|
+
char *start = ptr;
|
98
|
+
while (*ptr != '\0' && (*str == '\0' || strncmp(ptr, str, n) != 0)) ptr++;
|
99
|
+
return (unsigned)(ptr-start);
|
100
|
+
}
|
101
|
+
|
102
|
+
map <unsigned, string> pattern::_gen_indeces(char *str, char *ptr) const {
|
103
|
+
// advance to wildcard
|
104
|
+
while (*ptr != '\0' && !at_wildcard(ptr)) ptr++;
|
105
|
+
|
106
|
+
if (*ptr == '\0') return map <unsigned, string>();
|
107
|
+
unsigned idx = ptr-str;
|
108
|
+
|
109
|
+
// advance to characters after the pattern
|
110
|
+
char *start = ptr;
|
111
|
+
advance_past_wildcard(ptr);
|
112
|
+
unsigned token_len = (unsigned)(ptr-start);
|
113
|
+
|
114
|
+
map <unsigned, string> recursive = _gen_indeces(str, ptr);
|
115
|
+
recursive[idx] = string(str+idx,token_len);
|
116
|
+
return recursive;
|
117
|
+
}
|
118
|
+
|
119
|
+
//
|
120
|
+
// overrideable functions
|
121
|
+
//
|
122
|
+
|
123
|
+
bool pattern::at_wildcard(char * ptr) const {
|
124
|
+
return (*ptr == '*' || *ptr == '<');
|
125
|
+
}
|
126
|
+
|
127
|
+
void pattern::advance_past_wildcard(char *&ptr) const {
|
128
|
+
switch (*ptr) {
|
129
|
+
case '<': while (*ptr != '>') ptr++; ptr++; break;
|
130
|
+
case '*': ptr++; break;
|
131
|
+
default: break;
|
132
|
+
}
|
133
|
+
}
|
134
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <stdlib.h>
|
3
|
+
#include <string.h>
|
4
|
+
#include <stdbool.h>
|
5
|
+
#include <iostream>
|
6
|
+
#include <list>
|
7
|
+
#include <map>
|
8
|
+
|
9
|
+
// TODO:
|
10
|
+
// 1. write an iterator (exposes a value and a token)
|
11
|
+
// 2. make more extensible
|
12
|
+
// 3. improve _advance_to_str function to
|
13
|
+
|
14
|
+
namespace lpm {
|
15
|
+
using namespace std;
|
16
|
+
|
17
|
+
class pattern {
|
18
|
+
public:
|
19
|
+
pattern(const char *ptrn) { create(ptrn); }
|
20
|
+
pattern(string ptrn) { create(ptrn); }
|
21
|
+
|
22
|
+
bool operator==(const string &rhs) const;
|
23
|
+
bool operator==(const pattern &rhs) const;
|
24
|
+
bool operator!=(const string &rhs) const;
|
25
|
+
|
26
|
+
// returns true if there are any wilcards/splats/etc in this
|
27
|
+
bool is_dynamic() const;
|
28
|
+
|
29
|
+
// returns the pattern used
|
30
|
+
string pattern_str() const;
|
31
|
+
|
32
|
+
// check if a string matches the this pattern
|
33
|
+
bool matches(string comp) const;
|
34
|
+
|
35
|
+
// extracts all mappings from cppstr
|
36
|
+
map<string, string> extract_mappings(string cppstr) const;
|
37
|
+
|
38
|
+
// extracts all splats from cppstr
|
39
|
+
list<string> extract_splats(string cppstr) const;
|
40
|
+
protected:
|
41
|
+
string _pattern;
|
42
|
+
map<unsigned, string> _index;
|
43
|
+
|
44
|
+
void create(string ptrn);
|
45
|
+
|
46
|
+
// return true if ptr is at a wildcard sequence
|
47
|
+
virtual bool at_wildcard(char *ptr) const;
|
48
|
+
|
49
|
+
// advance ptr to the character beyond a wildcard sequence.
|
50
|
+
virtual void advance_past_wildcard(char *&ptr) const;
|
51
|
+
private:
|
52
|
+
// advance ptr to the next occurance of str
|
53
|
+
unsigned _advance_to_str(char *&ptr, char *str, unsigned len) const;
|
54
|
+
map <unsigned, string> _gen_indeces(char *str, char *ptr) const;
|
55
|
+
};
|
56
|
+
}
|
data/lib/sansom/sansomable.rb
CHANGED
@@ -24,7 +24,7 @@ module Sansomable
|
|
24
24
|
def _call_handler handler, *args
|
25
25
|
res = handler.call *args
|
26
26
|
res = res.finish if res.is_a? Rack::Response
|
27
|
-
raise ResponseError, "Response must either be a rack response, string, or object" unless Rack::Lint.fastlint res
|
27
|
+
raise ResponseError, "Response must either be a rack response, string, or object" unless Rack::Lint.fastlint res
|
28
28
|
res = [200, {}, [res.to_str]] if res.respond_to? :to_str
|
29
29
|
res
|
30
30
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sansom
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathaniel Symer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -48,18 +48,15 @@ extensions:
|
|
48
48
|
- ext/sansom/pine/extconf.rb
|
49
49
|
extra_rdoc_files: []
|
50
50
|
files:
|
51
|
-
- ".gitignore"
|
52
|
-
- Gemfile
|
53
|
-
- LICENSE.txt
|
54
|
-
- README.md
|
55
|
-
- changelog.md
|
56
51
|
- ext/sansom/pine/extconf.rb
|
52
|
+
- ext/sansom/pine/matcher.cpp
|
53
|
+
- ext/sansom/pine/pattern.cpp
|
54
|
+
- ext/sansom/pine/pattern.h
|
57
55
|
- lib/rack/fastlint.rb
|
58
56
|
- lib/sansom.rb
|
59
57
|
- lib/sansom/pine.rb
|
60
58
|
- lib/sansom/pine/node.rb
|
61
59
|
- lib/sansom/sansomable.rb
|
62
|
-
- sansom.gemspec
|
63
60
|
homepage: http://github.com/fhsjaagshs/sansom
|
64
61
|
licenses:
|
65
62
|
- MIT
|
data/.gitignore
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
ext/sansom/pine/Makefile
|
2
|
-
*.gem
|
3
|
-
*.rbc
|
4
|
-
.bundle
|
5
|
-
.config
|
6
|
-
.yardoc
|
7
|
-
Gemfile.lock
|
8
|
-
InstalledFiles
|
9
|
-
_yardoc
|
10
|
-
coverage
|
11
|
-
doc/
|
12
|
-
lib/bundler/man
|
13
|
-
pkg
|
14
|
-
rdoc
|
15
|
-
spec/reports
|
16
|
-
test/tmp
|
17
|
-
test/version_tmp
|
18
|
-
tmp
|
19
|
-
*.bundle
|
20
|
-
*.so
|
21
|
-
*.o
|
22
|
-
*.a
|
23
|
-
mkmf.log
|
data/Gemfile
DELETED
data/LICENSE.txt
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
Copyright (c) 2014 Nathaniel Symer
|
2
|
-
|
3
|
-
MIT License
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
a copy of this software and associated documentation files (the
|
7
|
-
"Software"), to deal in the Software without restriction, including
|
8
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
-
permit persons to whom the Software is furnished to do so, subject to
|
11
|
-
the following conditions:
|
12
|
-
|
13
|
-
The above copyright notice and this permission notice shall be
|
14
|
-
included in all copies or substantial portions of the Software.
|
15
|
-
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
DELETED
@@ -1,232 +0,0 @@
|
|
1
|
-
Sansom
|
2
|
-
===
|
3
|
-
|
4
|
-
No-nonsense web 'picowork' named after Sansom street in Philly, near where it was made.
|
5
|
-
|
6
|
-
Philosophy
|
7
|
-
-
|
8
|
-
|
9
|
-
***A framework should not limit you to one way of thinking.***
|
10
|
-
|
11
|
-
- You can write a `Sansomable` for each logical unit of your API, but you also don't have to.
|
12
|
-
|
13
|
-
- You can also mount existing Rails/Sinatra/Rack apps in your `Sansomable`. But you also don't have to.
|
14
|
-
|
15
|
-
- You can write one `Sansomable` for your entire API.
|
16
|
-
|
17
|
-
Fuck it.
|
18
|
-
|
19
|
-
***A tool should do one thing, and do it well. (Unix philosophy)***
|
20
|
-
|
21
|
-
A web framework is, fundamentally, a tool to connect code to a URL's path. Therefore, a web framework doesn't provide an ORM, template rendering, shortcuts, nor security patches.
|
22
|
-
|
23
|
-
***A web framework shall remain a framework***
|
24
|
-
|
25
|
-
No single tool should get so powerful that it can overcome its master. Rails and Sinatra have been doing this: modifying the language beyond recognition. Rails has activerecord and Sinatra's blocks aren't really blocks.
|
26
|
-
|
27
|
-
Installation
|
28
|
-
-
|
29
|
-
|
30
|
-
`gem install sansom`
|
31
|
-
|
32
|
-
Or, you can clone this repo and use `gem build sansom.gemspec` to build the gem.
|
33
|
-
|
34
|
-
Writing a Sansom app
|
35
|
-
-
|
36
|
-
Writing a one-file application is trivial with Sansom:
|
37
|
-
|
38
|
-
# config.ru
|
39
|
-
|
40
|
-
require "sansom"
|
41
|
-
|
42
|
-
class MyAPI
|
43
|
-
include Sansomable
|
44
|
-
def routes
|
45
|
-
# define routes here
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
run MyAPI.new
|
50
|
-
|
51
|
-
Defining Routes
|
52
|
-
-
|
53
|
-
Routes are defined through (dynamically resolved) instance methods that correspond to HTTP verbs. They take a path and a block. The block must be able to accept (at least) **one** argument.
|
54
|
-
|
55
|
-
You can either write
|
56
|
-
|
57
|
-
require "sansom"
|
58
|
-
|
59
|
-
class MyAPI
|
60
|
-
include Sansomable
|
61
|
-
def routes
|
62
|
-
get "/" do |r|
|
63
|
-
# r is a Rack::Request object
|
64
|
-
[200, {}, ["hello world!"]]
|
65
|
-
end
|
66
|
-
|
67
|
-
post "/form" do |r|
|
68
|
-
# return a Rack response
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
Routes can also be defined like so:
|
75
|
-
|
76
|
-
s = MyAPI.new
|
77
|
-
s.get "/" do |r| # r is a Rack::Request
|
78
|
-
[200, {}, ["Return a Rack response."]]
|
79
|
-
end
|
80
|
-
|
81
|
-
But let's say you have an existing Sinatra/Rails/Sansom (Rack) app. It's simple: mount them. For example, mounting existing applications can be used to easily version an app:
|
82
|
-
|
83
|
-
# config.ru
|
84
|
-
|
85
|
-
require "sansom"
|
86
|
-
|
87
|
-
class Versioning
|
88
|
-
include Sansomable
|
89
|
-
end
|
90
|
-
|
91
|
-
s = Versioning.new
|
92
|
-
s.mount "/v1", MyAPI.new
|
93
|
-
s.mount "/v2", MyNewAPI.new
|
94
|
-
|
95
|
-
run s
|
96
|
-
|
97
|
-
Sansom routes vs Sinatra routes
|
98
|
-
-
|
99
|
-
|
100
|
-
**Sansom routes remain true blocks**: When a route is mapped, the same block you use is called when a route is matched. It's the same object every time.
|
101
|
-
|
102
|
-
**Sinatra routes become methods behind the scenes**: When a route is matched, Sinatra looks up the method and calls it.
|
103
|
-
|
104
|
-
It's a common idiom in Sinatra to use `return` to terminate execution of a route prematurely (since Sinatra routes aren't blocks). **You must use `next` instead** (you can relplace all instances of `return` with `next`).
|
105
|
-
|
106
|
-
Before filters
|
107
|
-
-
|
108
|
-
|
109
|
-
You can write before filters to try to preëmpt request processing. If the block returns anything (other than nil) **the request is preëmpted**. In that case, the response from the before block is the response for the request.
|
110
|
-
|
111
|
-
# app.rb
|
112
|
-
|
113
|
-
require "sansom"
|
114
|
-
|
115
|
-
s = Sansom.new
|
116
|
-
s.before do |r|
|
117
|
-
[200, {}, ["Preëmpted."]] if some_condition
|
118
|
-
end
|
119
|
-
|
120
|
-
You could use this for request statistics, caching, auth, etc.
|
121
|
-
|
122
|
-
After filters
|
123
|
-
-
|
124
|
-
|
125
|
-
Called after a route is called. If they return a non-nil response, that response is used instead of the response from a route. After filters are not called if a before filter preëmpted route execution.
|
126
|
-
|
127
|
-
# app.rb
|
128
|
-
|
129
|
-
require "sansom"
|
130
|
-
|
131
|
-
s = Sansom.new
|
132
|
-
s.after do |req,res| # req is a Rack::Request and res is the response generated by a route.
|
133
|
-
next [200, {}, ["Postëmpted."]] if some_condition
|
134
|
-
end
|
135
|
-
|
136
|
-
Errors
|
137
|
-
-
|
138
|
-
|
139
|
-
Error blocks allow for the app to return something parseable when an error is raised.
|
140
|
-
|
141
|
-
require "sansom"
|
142
|
-
require "json"
|
143
|
-
|
144
|
-
s = Sansom.new
|
145
|
-
s.error do |r, err| # err is the error, r is a Rack::Request
|
146
|
-
[500, {"yo" => "headers"}, [{ :message => err.message }.to_json]]
|
147
|
-
end
|
148
|
-
|
149
|
-
There is also a unique error 404 handler:
|
150
|
-
|
151
|
-
require "sansom"
|
152
|
-
require "json"
|
153
|
-
|
154
|
-
s = Sansom.new
|
155
|
-
s.not_found do |r| # r is a Rack::Request
|
156
|
-
[404, {"yo" => "shit"}, [{ :message => "not found" }.to_json]]
|
157
|
-
end
|
158
|
-
|
159
|
-
Matching
|
160
|
-
-
|
161
|
-
|
162
|
-
`Sansom` uses trees to match routes. It follows a certain set of rules:
|
163
|
-
|
164
|
-
1. The route matching the path and verb. Routes have a sub-order:
|
165
|
-
1. "Static" paths
|
166
|
-
2. Wildcards (see below)
|
167
|
-
1. Full mappings (kinda a non-compete)
|
168
|
-
2. Partial mappings
|
169
|
-
3. Splats
|
170
|
-
3. The first Subsansom that matches the route & verb
|
171
|
-
4. The first mounted non-`Sansom` rack app matching the route
|
172
|
-
|
173
|
-
|
174
|
-
Wildcards
|
175
|
-
-
|
176
|
-
|
177
|
-
Sansom supports multiple wildcards:
|
178
|
-
|
179
|
-
`/path/to/:resource/:action` - Full mapping
|
180
|
-
`/path/to/resource.<format>` - Partial mapping
|
181
|
-
`/path/to/*.json` - Splat
|
182
|
-
`/path/to/*.<format>.<compression>` - You can mix them.
|
183
|
-
|
184
|
-
Mappings map part of the route (for example `format` above) to the corresponding part of the matched path (for `/resource.<format>` and `/resource.json` yields a mapping of `format`:`json`).
|
185
|
-
|
186
|
-
Mappings (full and partial) are available in `Rack::Request#params` **by name**, and splats are available under the key `splats` in `Rack::Request#params`.
|
187
|
-
|
188
|
-
*See the Matching section of this readme for wildcard precedence.*
|
189
|
-
|
190
|
-
|
191
|
-
Notes
|
192
|
-
-
|
193
|
-
|
194
|
-
- `Sansom` does not pollute _any_ `Object` methods, including `initialize`
|
195
|
-
- No regexes are used in the entire project.
|
196
|
-
- Has one dependency: `rack`
|
197
|
-
- `Sansom` is under **400** lines of code at the time of writing. This includes
|
198
|
-
* Rack conformity & the DSL (`sansom.rb`) (~90 lines)
|
199
|
-
* Custom tree-based routing (`sanom/pine.rb`) (~150 lines)
|
200
|
-
* libpatternmatch (~150 lines of C++)
|
201
|
-
|
202
|
-
Speed
|
203
|
-
-
|
204
|
-
|
205
|
-
Well, that's great and all, but how fast is "hello world" example in comparision to Rack or Sinatra?
|
206
|
-
|
207
|
-
Rack: **11ms**<br />
|
208
|
-
Sansom: **14ms**\*†<br />
|
209
|
-
Sinatra: **28ms**<br />
|
210
|
-
Rails: **34ms****
|
211
|
-
|
212
|
-
(results are measured locally using Puma and are rounded down)
|
213
|
-
|
214
|
-
Hey [Konstantine](https://github.com/rkh), *put that in your pipe and smoke it*.
|
215
|
-
|
216
|
-
\* Uncached. If a tree lookup is cached, it takes the same time as Rack.
|
217
|
-
|
218
|
-
† Sansom's speed (compared to Sinatra) may be because it doesn't load any middleware by default.
|
219
|
-
|
220
|
-
\** Rails loads a rich welcome page which may contribute to its slowness
|
221
|
-
|
222
|
-
Todo
|
223
|
-
-
|
224
|
-
|
225
|
-
* Multiple return types for routes
|
226
|
-
|
227
|
-
If you have any ideas, let me know!
|
228
|
-
|
229
|
-
Contributing
|
230
|
-
-
|
231
|
-
|
232
|
-
You know the drill. But ** make sure you don't add tons and tons of code. Part of `Sansom`'s beauty is its brevity.**
|
data/changelog.md
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
Changelog
|
2
|
-
=
|
3
|
-
|
4
|
-
0.0.1
|
5
|
-
|
6
|
-
(yanked due to bad name in Gemfile)
|
7
|
-
|
8
|
-
0.0.2
|
9
|
-
|
10
|
-
- Initial release
|
11
|
-
|
12
|
-
0.0.3
|
13
|
-
|
14
|
-
- Wrote custom tree implementation called Pine to replace RubyTree
|
15
|
-
- Added `before` block
|
16
|
-
|
17
|
-
Here's an example
|
18
|
-
|
19
|
-
s = Sansom.new
|
20
|
-
|
21
|
-
s.before do |r|
|
22
|
-
# Caveat: routes are mapped before this block is called
|
23
|
-
# Code executed before mapped route
|
24
|
-
end
|
25
|
-
|
26
|
-
s.get "/" do |r|
|
27
|
-
[200, {}, ["Hello!"]]
|
28
|
-
end
|
29
|
-
|
30
|
-
s.start 2000
|
31
|
-
|
32
|
-
0.0.4
|
33
|
-
|
34
|
-
- Fixed bug with with requiring pine
|
35
|
-
|
36
|
-
0.0.5
|
37
|
-
|
38
|
-
- Parameterized URLs!!! (Stuff like `/user/:id/profile`)
|
39
|
-
* Parameterized URLs work with mounted Rack/Sansom apps
|
40
|
-
- Improved matching efficiency
|
41
|
-
|
42
|
-
0.0.6
|
43
|
-
|
44
|
-
- `before` block response checking
|
45
|
-
|
46
|
-
0.0.7
|
47
|
-
|
48
|
-
- Fixed bug where a wilcard path component would be ignored if it came last in the URL
|
49
|
-
- Fixed a bug where async responses would be marked as bad by the fastlinter.
|
50
|
-
|
51
|
-
0.1.0
|
52
|
-
|
53
|
-
- PUBLIC RELEASE!
|
54
|
-
- `after` block
|
55
|
-
- Improved routing behavior & speed
|
56
|
-
|
57
|
-
0.1.1
|
58
|
-
|
59
|
-
- Fix bad bug in method_missing
|
60
|
-
- Added better error handling (per-error handling and a generic block that gets called if no specific handler is present)
|
61
|
-
|
62
|
-
0.1.2
|
63
|
-
|
64
|
-
- Fixed issue with `include` in the `Sansom` class
|
65
|
-
|
66
|
-
0.2.0
|
67
|
-
|
68
|
-
- Rewrite internals to:
|
69
|
-
1. Avoid collisions with the including class
|
70
|
-
2. Improve performance
|
71
|
-
3. Look better
|
72
|
-
4. **Avoid bugs**
|
73
|
-
|
74
|
-
- Route match caching by path and HTTP method
|
75
|
-
->Should improve performance for static paths dramatically
|
76
|
-
|
data/sansom.gemspec
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
|
5
|
-
Gem::Specification.new do |s|
|
6
|
-
s.name = "sansom"
|
7
|
-
s.version = "0.3.1"
|
8
|
-
s.authors = ["Nathaniel Symer"]
|
9
|
-
s.email = ["nate@natesymer.com"]
|
10
|
-
s.summary = "Scientific, philosophical, abstract web 'picowork' named after Sansom street in Philly, near where it was made."
|
11
|
-
s.description = s.summary + " " + "It's under 200 lines of code & it's lightning fast. It uses tree-based route resolution."
|
12
|
-
s.homepage = "http://github.com/fhsjaagshs/sansom"
|
13
|
-
s.license = "MIT"
|
14
|
-
|
15
|
-
allfiles = `git ls-files -z`.split("\x0")
|
16
|
-
s.files = allfiles.grep(%r{(^[^\/]*$|^lib\/)}) # Match all lib files AND files in the root
|
17
|
-
s.extensions = ["ext/sansom/pine/extconf.rb"]
|
18
|
-
s.executables = allfiles.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
-
s.test_files = allfiles.grep(%r{^(test|spec|features)/})
|
20
|
-
s.require_paths = ["lib"]
|
21
|
-
|
22
|
-
s.add_development_dependency "bundler", "~> 1.6"
|
23
|
-
s.add_dependency "rack", "~> 1"
|
24
|
-
end
|