shift-lang 0.1.1

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.
Files changed (42) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +22 -0
  4. data/README.md +29 -0
  5. data/Rakefile +2 -0
  6. data/bin/sft +5 -0
  7. data/bin/shift +5 -0
  8. data/lib/app_engine_templates/app.yaml +13 -0
  9. data/lib/app_engine_templates/pystache/__init__.py +8 -0
  10. data/lib/app_engine_templates/pystache/__init__.pyc +0 -0
  11. data/lib/app_engine_templates/pystache/loader.py +47 -0
  12. data/lib/app_engine_templates/pystache/loader.pyc +0 -0
  13. data/lib/app_engine_templates/pystache/template.py +177 -0
  14. data/lib/app_engine_templates/pystache/template.pyc +0 -0
  15. data/lib/app_engine_templates/pystache/view.py +94 -0
  16. data/lib/app_engine_templates/pystache/view.pyc +0 -0
  17. data/lib/heroku_templates/Gemfile +6 -0
  18. data/lib/heroku_templates/Procfile +1 -0
  19. data/lib/shift-lang/builder/db_model.rb +20 -0
  20. data/lib/shift-lang/builder/url_handler.rb +17 -0
  21. data/lib/shift-lang/builder.rb +103 -0
  22. data/lib/shift-lang/generator/javascript_generator.rb +410 -0
  23. data/lib/shift-lang/generator/javascript_templates.rb +37 -0
  24. data/lib/shift-lang/generator/python_generator.rb +330 -0
  25. data/lib/shift-lang/generator/python_templates.rb +44 -0
  26. data/lib/shift-lang/generator/ruby_generator.rb +334 -0
  27. data/lib/shift-lang/generator/ruby_templates.rb +50 -0
  28. data/lib/shift-lang/generator.rb +41 -0
  29. data/lib/shift-lang/parser/control_statement_parser.rb +17 -0
  30. data/lib/shift-lang/parser/db_query_parser.rb +20 -0
  31. data/lib/shift-lang/parser/keyword_parser.rb +52 -0
  32. data/lib/shift-lang/parser/operator_parser.rb +35 -0
  33. data/lib/shift-lang/parser/routing_expression_parser.rb +33 -0
  34. data/lib/shift-lang/parser/simple_expression_parser.rb +87 -0
  35. data/lib/shift-lang/parser/token_parser.rb +69 -0
  36. data/lib/shift-lang/parser/value_returning_expression_parser.rb +18 -0
  37. data/lib/shift-lang/parser/void_statement_parser.rb +12 -0
  38. data/lib/shift-lang/parser.rb +120 -0
  39. data/lib/shift-lang/version.rb +3 -0
  40. data/lib/shift-lang.rb +224 -0
  41. data/shift-lang.gemspec +19 -0
  42. metadata +106 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in shift-lang.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/sft ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/shift-lang'
4
+
5
+ Shift::CLI.new(ARGV)
data/bin/shift ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/shift-lang'
4
+
5
+ Shift::CLI.new(ARGV)
@@ -0,0 +1,13 @@
1
+ application: <%= project_name %>
2
+ version: 1
3
+ runtime: python27
4
+ api_version: 1
5
+ threadsafe: false
6
+
7
+ libraries:
8
+ - name: markupsafe
9
+ version: latest
10
+
11
+ handlers:
12
+ - url: /.*
13
+ script: <%= project_name %>.app
@@ -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()
@@ -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
@@ -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
@@ -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()
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+ gem 'sinatra', '1.3.2'
3
+ gem 'data_mapper'
4
+ gem 'pg'
5
+ gem 'dm-postgres-adapter'
6
+ gem 'mustache'
@@ -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