sansom 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|