shift-lang 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/bin/sft +5 -0
- data/bin/shift +5 -0
- data/lib/app_engine_templates/app.yaml +13 -0
- data/lib/app_engine_templates/pystache/__init__.py +8 -0
- data/lib/app_engine_templates/pystache/__init__.pyc +0 -0
- data/lib/app_engine_templates/pystache/loader.py +47 -0
- data/lib/app_engine_templates/pystache/loader.pyc +0 -0
- data/lib/app_engine_templates/pystache/template.py +177 -0
- data/lib/app_engine_templates/pystache/template.pyc +0 -0
- data/lib/app_engine_templates/pystache/view.py +94 -0
- data/lib/app_engine_templates/pystache/view.pyc +0 -0
- data/lib/heroku_templates/Gemfile +6 -0
- data/lib/heroku_templates/Procfile +1 -0
- data/lib/shift-lang/builder/db_model.rb +20 -0
- data/lib/shift-lang/builder/url_handler.rb +17 -0
- data/lib/shift-lang/builder.rb +103 -0
- data/lib/shift-lang/generator/javascript_generator.rb +410 -0
- data/lib/shift-lang/generator/javascript_templates.rb +37 -0
- data/lib/shift-lang/generator/python_generator.rb +330 -0
- data/lib/shift-lang/generator/python_templates.rb +44 -0
- data/lib/shift-lang/generator/ruby_generator.rb +334 -0
- data/lib/shift-lang/generator/ruby_templates.rb +50 -0
- data/lib/shift-lang/generator.rb +41 -0
- data/lib/shift-lang/parser/control_statement_parser.rb +17 -0
- data/lib/shift-lang/parser/db_query_parser.rb +20 -0
- data/lib/shift-lang/parser/keyword_parser.rb +52 -0
- data/lib/shift-lang/parser/operator_parser.rb +35 -0
- data/lib/shift-lang/parser/routing_expression_parser.rb +33 -0
- data/lib/shift-lang/parser/simple_expression_parser.rb +87 -0
- data/lib/shift-lang/parser/token_parser.rb +69 -0
- data/lib/shift-lang/parser/value_returning_expression_parser.rb +18 -0
- data/lib/shift-lang/parser/void_statement_parser.rb +12 -0
- data/lib/shift-lang/parser.rb +120 -0
- data/lib/shift-lang/version.rb +3 -0
- data/lib/shift-lang.rb +224 -0
- data/shift-lang.gemspec +19 -0
- metadata +106 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Pradeek
|
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
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Shift::Lang
|
2
|
+
|
3
|
+
Shift is a Domain-Specific Language that can be used to quickly write server-side applications
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'shift-lang'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install shift-lang
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/sft
ADDED
data/bin/shift
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
from pystache.template import Template
|
2
|
+
from pystache.view import View
|
3
|
+
from pystache.loader import Loader
|
4
|
+
|
5
|
+
def render(template, context=None, **kwargs):
|
6
|
+
context = context and context.copy() or {}
|
7
|
+
context.update(kwargs)
|
8
|
+
return Template(template, context).render()
|
Binary file
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
class Loader(object):
|
4
|
+
|
5
|
+
template_extension = 'mustache'
|
6
|
+
template_path = '.'
|
7
|
+
template_encoding = None
|
8
|
+
|
9
|
+
def load_template(self, template_name, template_dirs=None, encoding=None, extension=None):
|
10
|
+
'''Returns the template string from a file or throws IOError if it non existent'''
|
11
|
+
if None == template_dirs:
|
12
|
+
template_dirs = self.template_path
|
13
|
+
|
14
|
+
if encoding is not None:
|
15
|
+
self.template_encoding = encoding
|
16
|
+
|
17
|
+
if extension is not None:
|
18
|
+
self.template_extension = extension
|
19
|
+
|
20
|
+
file_name = template_name + '.' + self.template_extension
|
21
|
+
|
22
|
+
# Given a single directory we'll load from it
|
23
|
+
if isinstance(template_dirs, basestring):
|
24
|
+
file_path = os.path.join(template_dirs, file_name)
|
25
|
+
|
26
|
+
return self._load_template_file(file_path)
|
27
|
+
|
28
|
+
# Given a list of directories we'll check each for our file
|
29
|
+
for path in template_dirs:
|
30
|
+
file_path = os.path.join(path, file_name)
|
31
|
+
if os.path.exists(file_path):
|
32
|
+
return self._load_template_file(file_path)
|
33
|
+
|
34
|
+
raise IOError('"%s" not found in "%s"' % (template_name, ':'.join(template_dirs),))
|
35
|
+
|
36
|
+
def _load_template_file(self, file_path):
|
37
|
+
'''Loads and returns the template file from disk'''
|
38
|
+
f = open(file_path, 'r')
|
39
|
+
|
40
|
+
try:
|
41
|
+
template = f.read()
|
42
|
+
if self.template_encoding:
|
43
|
+
template = unicode(template, self.template_encoding)
|
44
|
+
finally:
|
45
|
+
f.close()
|
46
|
+
|
47
|
+
return template
|
Binary file
|
@@ -0,0 +1,177 @@
|
|
1
|
+
import re
|
2
|
+
import cgi
|
3
|
+
import collections
|
4
|
+
import os
|
5
|
+
import copy
|
6
|
+
|
7
|
+
try:
|
8
|
+
import markupsafe
|
9
|
+
escape = markupsafe.escape
|
10
|
+
literal = markupsafe.Markup
|
11
|
+
|
12
|
+
except ImportError:
|
13
|
+
escape = lambda x: cgi.escape(unicode(x))
|
14
|
+
literal = unicode
|
15
|
+
|
16
|
+
|
17
|
+
class Modifiers(dict):
|
18
|
+
"""Dictionary with a decorator for assigning functions to keys."""
|
19
|
+
|
20
|
+
def set(self, key):
|
21
|
+
"""
|
22
|
+
Decorator function to set the given key to the decorated function.
|
23
|
+
|
24
|
+
>>> modifiers = {}
|
25
|
+
>>> @modifiers.set('P')
|
26
|
+
... def render_tongue(self, tag_name=None, context=None):
|
27
|
+
... return ":P %s" % tag_name
|
28
|
+
>>> modifiers
|
29
|
+
{'P': <function render_tongue at 0x...>}
|
30
|
+
"""
|
31
|
+
|
32
|
+
def setter(func):
|
33
|
+
self[key] = func
|
34
|
+
return func
|
35
|
+
return setter
|
36
|
+
|
37
|
+
|
38
|
+
class Template(object):
|
39
|
+
|
40
|
+
tag_re = None
|
41
|
+
|
42
|
+
otag = '{{'
|
43
|
+
|
44
|
+
ctag = '}}'
|
45
|
+
|
46
|
+
modifiers = Modifiers()
|
47
|
+
|
48
|
+
def __init__(self, template=None, context=None, **kwargs):
|
49
|
+
from view import View
|
50
|
+
|
51
|
+
self.template = template
|
52
|
+
|
53
|
+
if kwargs:
|
54
|
+
context.update(kwargs)
|
55
|
+
|
56
|
+
self.view = context if isinstance(context, View) else View(context=context)
|
57
|
+
self._compile_regexps()
|
58
|
+
|
59
|
+
def _compile_regexps(self):
|
60
|
+
tags = {
|
61
|
+
'otag': re.escape(self.otag),
|
62
|
+
'ctag': re.escape(self.ctag)
|
63
|
+
}
|
64
|
+
|
65
|
+
section = r"%(otag)s[\#|^]([^\}]*)%(ctag)s\s*(.+?\s*)%(otag)s/\1%(ctag)s"
|
66
|
+
self.section_re = re.compile(section % tags, re.M|re.S)
|
67
|
+
|
68
|
+
tag = r"%(otag)s(#|=|&|!|>|\{)?(.+?)\1?%(ctag)s+"
|
69
|
+
self.tag_re = re.compile(tag % tags)
|
70
|
+
|
71
|
+
def _render_sections(self, template, view):
|
72
|
+
while True:
|
73
|
+
match = self.section_re.search(template)
|
74
|
+
if match is None:
|
75
|
+
break
|
76
|
+
|
77
|
+
section, section_name, inner = match.group(0, 1, 2)
|
78
|
+
section_name = section_name.strip()
|
79
|
+
it = self.view.get(section_name, None)
|
80
|
+
replacer = ''
|
81
|
+
|
82
|
+
# Callable
|
83
|
+
if it and isinstance(it, collections.Callable):
|
84
|
+
replacer = it(inner)
|
85
|
+
# Dictionary
|
86
|
+
elif it and hasattr(it, 'keys') and hasattr(it, '__getitem__'):
|
87
|
+
if section[2] != '^':
|
88
|
+
replacer = self._render_dictionary(inner, it)
|
89
|
+
# Lists
|
90
|
+
elif it and hasattr(it, '__iter__'):
|
91
|
+
if section[2] != '^':
|
92
|
+
replacer = self._render_list(inner, it)
|
93
|
+
# Other objects
|
94
|
+
elif it and isinstance(it, object):
|
95
|
+
if section[2] != '^':
|
96
|
+
replacer = self._render_dictionary(inner, it)
|
97
|
+
# Falsey and Negated or Truthy and Not Negated
|
98
|
+
elif (not it and section[2] == '^') or (it and section[2] != '^'):
|
99
|
+
replacer = self._render_dictionary(inner, it)
|
100
|
+
|
101
|
+
template = literal(template.replace(section, replacer))
|
102
|
+
|
103
|
+
return template
|
104
|
+
|
105
|
+
def _render_tags(self, template):
|
106
|
+
while True:
|
107
|
+
match = self.tag_re.search(template)
|
108
|
+
if match is None:
|
109
|
+
break
|
110
|
+
|
111
|
+
tag, tag_type, tag_name = match.group(0, 1, 2)
|
112
|
+
tag_name = tag_name.strip()
|
113
|
+
func = self.modifiers[tag_type]
|
114
|
+
replacement = func(self, tag_name)
|
115
|
+
template = template.replace(tag, replacement)
|
116
|
+
|
117
|
+
return template
|
118
|
+
|
119
|
+
def _render_dictionary(self, template, context):
|
120
|
+
self.view.context_list.insert(0, context)
|
121
|
+
template = Template(template, self.view)
|
122
|
+
out = template.render()
|
123
|
+
self.view.context_list.pop(0)
|
124
|
+
return out
|
125
|
+
|
126
|
+
def _render_list(self, template, listing):
|
127
|
+
insides = []
|
128
|
+
for item in listing:
|
129
|
+
insides.append(self._render_dictionary(template, item))
|
130
|
+
|
131
|
+
return ''.join(insides)
|
132
|
+
|
133
|
+
@modifiers.set(None)
|
134
|
+
def _render_tag(self, tag_name):
|
135
|
+
raw = self.view.get(tag_name, '')
|
136
|
+
|
137
|
+
# For methods with no return value
|
138
|
+
if not raw and raw is not 0:
|
139
|
+
if tag_name == '.':
|
140
|
+
raw = self.view.context_list[0]
|
141
|
+
else:
|
142
|
+
return ''
|
143
|
+
|
144
|
+
return escape(raw)
|
145
|
+
|
146
|
+
@modifiers.set('!')
|
147
|
+
def _render_comment(self, tag_name):
|
148
|
+
return ''
|
149
|
+
|
150
|
+
@modifiers.set('>')
|
151
|
+
def _render_partial(self, template_name):
|
152
|
+
from pystache import Loader
|
153
|
+
markup = Loader().load_template(template_name, self.view.template_path, encoding=self.view.template_encoding)
|
154
|
+
template = Template(markup, self.view)
|
155
|
+
return template.render()
|
156
|
+
|
157
|
+
@modifiers.set('=')
|
158
|
+
def _change_delimiter(self, tag_name):
|
159
|
+
"""Changes the Mustache delimiter."""
|
160
|
+
self.otag, self.ctag = tag_name.split(' ')
|
161
|
+
self._compile_regexps()
|
162
|
+
return ''
|
163
|
+
|
164
|
+
@modifiers.set('{')
|
165
|
+
@modifiers.set('&')
|
166
|
+
def render_unescaped(self, tag_name):
|
167
|
+
"""Render a tag without escaping it."""
|
168
|
+
return literal(self.view.get(tag_name, ''))
|
169
|
+
|
170
|
+
def render(self, encoding=None):
|
171
|
+
template = self._render_sections(self.template, self.view)
|
172
|
+
result = self._render_tags(template)
|
173
|
+
|
174
|
+
if encoding is not None:
|
175
|
+
result = result.encode(encoding)
|
176
|
+
|
177
|
+
return result
|
Binary file
|
@@ -0,0 +1,94 @@
|
|
1
|
+
from pystache import Template
|
2
|
+
import os.path
|
3
|
+
import re
|
4
|
+
from types import *
|
5
|
+
|
6
|
+
def get_or_attr(context_list, name, default=None):
|
7
|
+
if not context_list:
|
8
|
+
return default
|
9
|
+
|
10
|
+
for obj in context_list:
|
11
|
+
try:
|
12
|
+
return obj[name]
|
13
|
+
except KeyError:
|
14
|
+
pass
|
15
|
+
except:
|
16
|
+
try:
|
17
|
+
return getattr(obj, name)
|
18
|
+
except AttributeError:
|
19
|
+
pass
|
20
|
+
return default
|
21
|
+
|
22
|
+
class View(object):
|
23
|
+
|
24
|
+
template_name = None
|
25
|
+
template_path = None
|
26
|
+
template = None
|
27
|
+
template_encoding = None
|
28
|
+
template_extension = 'mustache'
|
29
|
+
|
30
|
+
def __init__(self, template=None, context=None, **kwargs):
|
31
|
+
self.template = template
|
32
|
+
context = context or {}
|
33
|
+
context.update(**kwargs)
|
34
|
+
|
35
|
+
self.context_list = [context]
|
36
|
+
|
37
|
+
def get(self, attr, default=None):
|
38
|
+
attr = get_or_attr(self.context_list, attr, getattr(self, attr, default))
|
39
|
+
if hasattr(attr, '__call__') and type(attr) is UnboundMethodType:
|
40
|
+
return attr()
|
41
|
+
else:
|
42
|
+
return attr
|
43
|
+
|
44
|
+
def get_template(self, template_name):
|
45
|
+
if not self.template:
|
46
|
+
from pystache import Loader
|
47
|
+
template_name = self._get_template_name(template_name)
|
48
|
+
self.template = Loader().load_template(template_name, self.template_path, encoding=self.template_encoding, extension=self.template_extension)
|
49
|
+
|
50
|
+
return self.template
|
51
|
+
|
52
|
+
def _get_template_name(self, template_name=None):
|
53
|
+
"""TemplatePartial => template_partial
|
54
|
+
Takes a string but defaults to using the current class' name or
|
55
|
+
the `template_name` attribute
|
56
|
+
"""
|
57
|
+
if template_name:
|
58
|
+
return template_name
|
59
|
+
|
60
|
+
template_name = self.__class__.__name__
|
61
|
+
|
62
|
+
def repl(match):
|
63
|
+
return '_' + match.group(0).lower()
|
64
|
+
|
65
|
+
return re.sub('[A-Z]', repl, template_name)[1:]
|
66
|
+
|
67
|
+
def _get_context(self):
|
68
|
+
context = {}
|
69
|
+
for item in self.context_list:
|
70
|
+
if hasattr(item, 'keys') and hasattr(item, '__getitem__'):
|
71
|
+
context.update(item)
|
72
|
+
return context
|
73
|
+
|
74
|
+
def render(self, encoding=None):
|
75
|
+
return Template(self.get_template(self.template_name), self).render(encoding=encoding)
|
76
|
+
|
77
|
+
def __contains__(self, needle):
|
78
|
+
return needle in self.context or hasattr(self, needle)
|
79
|
+
|
80
|
+
def __getitem__(self, attr):
|
81
|
+
val = self.get(attr, None)
|
82
|
+
|
83
|
+
if not val and val is not 0:
|
84
|
+
raise KeyError("Key '%s' does not exist in View" % attr)
|
85
|
+
return val
|
86
|
+
|
87
|
+
def __getattr__(self, attr):
|
88
|
+
if attr == 'context':
|
89
|
+
return self._get_context()
|
90
|
+
|
91
|
+
raise AttributeError("Attribute '%s' does not exist in View" % attr)
|
92
|
+
|
93
|
+
def __str__(self):
|
94
|
+
return self.render()
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
web: bundle exec ruby app.rb -p $PORT
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Shift
|
2
|
+
module Builder
|
3
|
+
class DBModel
|
4
|
+
attr_accessor :name, :attributes
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name = name.to_s
|
8
|
+
@attributes = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_attribute(name, type)
|
12
|
+
attribute = {
|
13
|
+
:name => name.to_s,
|
14
|
+
:type => type.to_s
|
15
|
+
}
|
16
|
+
@attributes.push attribute
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Shift
|
2
|
+
module Builder
|
3
|
+
class UrlHandler
|
4
|
+
attr_accessor :url, :method, :handler
|
5
|
+
|
6
|
+
def initialize(url, method)
|
7
|
+
@url = url
|
8
|
+
@method = method
|
9
|
+
@handler = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_statement(statement)
|
13
|
+
handler.push statement
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require_relative 'builder/db_model'
|
2
|
+
require_relative 'builder/url_handler'
|
3
|
+
|
4
|
+
require_relative 'parser'
|
5
|
+
|
6
|
+
module Shift
|
7
|
+
class ShiftBuilder
|
8
|
+
attr_accessor :models, :handlers, :clean
|
9
|
+
|
10
|
+
def initialize()
|
11
|
+
@models = []
|
12
|
+
@handlers = []
|
13
|
+
@clean = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_data_from_file(file_name)
|
17
|
+
parser = Shift::ShiftParser.new
|
18
|
+
|
19
|
+
model = nil
|
20
|
+
|
21
|
+
url_handler = nil
|
22
|
+
|
23
|
+
last_statement = ""
|
24
|
+
this_statement = ""
|
25
|
+
|
26
|
+
url = ""
|
27
|
+
|
28
|
+
line_number = 0
|
29
|
+
|
30
|
+
File.open(file_name) do |file|
|
31
|
+
while line = file.gets
|
32
|
+
parts = line.split("\t")
|
33
|
+
num_tabs = parts.length - 1
|
34
|
+
line = line.strip
|
35
|
+
line_number += 1
|
36
|
+
if line != ""
|
37
|
+
begin
|
38
|
+
statement = parser.parse(line)
|
39
|
+
|
40
|
+
this_statement = statement.keys[0]
|
41
|
+
|
42
|
+
case this_statement
|
43
|
+
|
44
|
+
when :model_definition_statement
|
45
|
+
model = Builder::DBModel.new(statement[:model_definition_statement][:model_name])
|
46
|
+
|
47
|
+
when :model_attribute_definition_statement
|
48
|
+
model_attribute = statement[:model_attribute_definition_statement]
|
49
|
+
model.add_attribute(model_attribute[:attribute_name], model_attribute[:attribute_type])
|
50
|
+
|
51
|
+
when :url
|
52
|
+
if (last_statement == :model_attribute_definition_statement)
|
53
|
+
add_model model
|
54
|
+
end
|
55
|
+
|
56
|
+
url = statement[:url]
|
57
|
+
when :url_method
|
58
|
+
if url_handler
|
59
|
+
add_handler url_handler
|
60
|
+
end
|
61
|
+
|
62
|
+
url_handler = Builder::UrlHandler.new(url, statement[:url_method].to_s)
|
63
|
+
else
|
64
|
+
stmt = {
|
65
|
+
:statement => statement,
|
66
|
+
:num_tabs => num_tabs
|
67
|
+
}
|
68
|
+
if !url_handler
|
69
|
+
url_handler = Builder::UrlHandler.new("", "")
|
70
|
+
end
|
71
|
+
url_handler.add_statement stmt
|
72
|
+
end
|
73
|
+
|
74
|
+
last_statement = this_statement
|
75
|
+
|
76
|
+
rescue Parslet::ParseFailed => parse_error
|
77
|
+
error = parse_error.to_s
|
78
|
+
error = error.split(" ")
|
79
|
+
puts "Error at line #{line_number} character #{error[error.length - 1]} : #{line}"
|
80
|
+
@clean = false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
if (last_statement == :model_attribute_definition_statement)
|
87
|
+
add_model model
|
88
|
+
else
|
89
|
+
add_handler url_handler
|
90
|
+
end
|
91
|
+
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_model(model)
|
96
|
+
@models.push model
|
97
|
+
end
|
98
|
+
|
99
|
+
def add_handler(handler)
|
100
|
+
@handlers.push handler
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|