grass 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.sass-cache/e3d4c2039fc7a8446e752aad5ac08f85d7457f92/(__TEMPLATE__)c +0 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +10 -0
- data/bin/grass +89 -0
- data/config/grass.rb +27 -0
- data/db/migrate/1_create_grass_sources.rb +24 -0
- data/grass.gemspec +43 -0
- data/lib/grass.rb +68 -0
- data/lib/grass/cache.rb +52 -0
- data/lib/grass/core_ext/kernel.rb +8 -0
- data/lib/grass/endpoints/api.rb +70 -0
- data/lib/grass/endpoints/front.rb +122 -0
- data/lib/grass/file_sync.rb +70 -0
- data/lib/grass/goliath/rack/auth_barrier.rb +109 -0
- data/lib/grass/goliath/rack/cache.rb +37 -0
- data/lib/grass/goliath/rack/cors.rb +45 -0
- data/lib/grass/goliath/rack/secure_headers.rb +20 -0
- data/lib/grass/goliath/rack/validator.rb +52 -0
- data/lib/grass/helpers/i18n_helper.rb +91 -0
- data/lib/grass/helpers/render_helper.rb +35 -0
- data/lib/grass/key.rb +137 -0
- data/lib/grass/render.rb +27 -0
- data/lib/grass/render/layout.rb +11 -0
- data/lib/grass/render/page.rb +31 -0
- data/lib/grass/render/renderer.rb +35 -0
- data/lib/grass/render/script.rb +27 -0
- data/lib/grass/render/stylesheet.rb +13 -0
- data/lib/grass/render/text.rb +11 -0
- data/lib/grass/render/view.rb +34 -0
- data/lib/grass/render/yui_renderer.rb +27 -0
- data/lib/grass/source.rb +107 -0
- data/lib/grass/tasks/db.rake +67 -0
- data/lib/grass/version.rb +3 -0
- data/lib/templates/app/Gemfile +9 -0
- data/lib/templates/app/Procfile +3 -0
- data/lib/templates/app/Rakefile +7 -0
- data/lib/templates/app/app/assets/scripts/application.en.js.coffee +1 -0
- data/lib/templates/app/app/assets/stylesheets/application.en.css.scss +4 -0
- data/lib/templates/app/app/content/pages/about.en.html.erb +3 -0
- data/lib/templates/app/app/content/pages/index.en.md.erb +5 -0
- data/lib/templates/app/app/views/layouts/application.en.html.erb +14 -0
- data/lib/templates/app/app/views/pages/show.en.html.erb +4 -0
- data/lib/templates/app/app/views/shared.en.html.erb +9 -0
- data/lib/templates/app/config/cache.yml +20 -0
- data/lib/templates/app/config/database.yml +35 -0
- data/lib/templates/app/config/grass.rb +27 -0
- data/lib/templates/app/db/migrate/1_create_grass_sources.rb +24 -0
- data/lib/templates/app/haproxy.cfg +43 -0
- data/lib/templates/app/public/favicon.ico +0 -0
- data/lib/templates/app/public/robots.txt +2 -0
- data/lib/templates/app/server.rb +7 -0
- data/test/dummy/app/content/texts/testapi.en.txt +1 -0
- data/test/dummy/config/cache.yml +23 -0
- data/test/dummy/config/database.yml +35 -0
- data/test/dummy/config/dummy.rb +1 -0
- data/test/dummy/config/haproxy.cfg +37 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/robots.txt +2 -0
- data/test/minitest_helper.rb +38 -0
- data/test/support/grass.rb +21 -0
- data/test/support/test.jpg +0 -0
- data/test/test_api.rb +74 -0
- data/test/test_front.rb +52 -0
- data/test/test_key.rb +118 -0
- data/test/test_source.rb +51 -0
- data/test/test_source_file.rb +47 -0
- data/test/test_source_render.rb +54 -0
- data/vendor/yuicompressor-2.4.8.jar +0 -0
- metadata +399 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
require "i18n"
|
2
|
+
require "ip_country"
|
3
|
+
|
4
|
+
module Grass
|
5
|
+
module Helpers
|
6
|
+
module I18nHelper
|
7
|
+
|
8
|
+
LNG_EXP_PATH = /^\/[a-z]{2}\-[A-Z]{2}\/|^\/[a-z]{2}\-[A-Z]{2}$|^\/[a-z]{2}\/|^\/[a-z]{2}$/
|
9
|
+
LNG_EXP_HTTP = /[a-z]{2}\-[A-Z]{2}|[a-z]{2}/
|
10
|
+
DEV_IP = ENV['DEV_IP'] || "86.185.186.43"
|
11
|
+
|
12
|
+
module_function
|
13
|
+
|
14
|
+
# Hash to collect all language related info
|
15
|
+
def language_info
|
16
|
+
{ current: I18n.locale, path: path_locale,
|
17
|
+
browser: browser_locale, country: country_locales
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
# ======================
|
22
|
+
# Language Detection
|
23
|
+
# ======================
|
24
|
+
|
25
|
+
# Set current locale
|
26
|
+
def set_locale
|
27
|
+
I18n.locale = locale
|
28
|
+
end
|
29
|
+
|
30
|
+
# Set locale by precedence path, browser, country, default
|
31
|
+
def locale
|
32
|
+
return path_locale if !path_locale.nil? && I18n.available_locales.include?(path_locale)
|
33
|
+
|
34
|
+
country_locales.each do |country_locale|
|
35
|
+
return country_locale if I18n.available_locales.include? country_locale
|
36
|
+
end
|
37
|
+
|
38
|
+
country_locales.each do |country_locale|
|
39
|
+
country_locale = country_locale.split("-").first.to_sym
|
40
|
+
return country_locale if I18n.available_locales.include? country_locale
|
41
|
+
end
|
42
|
+
|
43
|
+
return browser_locale if I18n.available_locales.include? browser_locale
|
44
|
+
|
45
|
+
I18n.default_locale
|
46
|
+
end
|
47
|
+
|
48
|
+
# Extract locale from request path
|
49
|
+
def path_locale
|
50
|
+
if path_locale = env['REQUEST_PATH'].scan(LNG_EXP_PATH).first
|
51
|
+
path_locale.gsub("/","").to_sym
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Extract locale from accept language header
|
56
|
+
def browser_locale
|
57
|
+
env['HTTP_ACCEPT_LANGUAGE'].scan(LNG_EXP_HTTP).first.to_sym unless env['HTTP_ACCEPT_LANGUAGE'].nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Get all spoken languages in a country sorted by speakers count
|
61
|
+
def country_locales
|
62
|
+
country_info ? country_info[:languages].split(",") : []
|
63
|
+
end
|
64
|
+
|
65
|
+
# ======================
|
66
|
+
# Ip to Country
|
67
|
+
# ======================
|
68
|
+
|
69
|
+
# Detailed country info
|
70
|
+
def country_info
|
71
|
+
begin
|
72
|
+
config['ip_country'].info(remote_ip)
|
73
|
+
rescue
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Client's IP4 Address
|
79
|
+
# Stolen from Rack::Request
|
80
|
+
def remote_ip
|
81
|
+
return (ENV["REMOTE_IP"]||=DEV_IP) if Goliat.env != :production
|
82
|
+
if addr = env['HTTP_X_FORWARDED_FOR']
|
83
|
+
(addr.split(',').grep(/\d\./).first || env['REMOTE_ADDR']).to_s.strip
|
84
|
+
else
|
85
|
+
env['REMOTE_ADDR']
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'grass/source'
|
2
|
+
|
3
|
+
module Grass
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
module RenderHelper
|
7
|
+
|
8
|
+
def include_partial key
|
9
|
+
source = Source[key].first || init_source_if_file_exists(key)
|
10
|
+
source.try(:raw)
|
11
|
+
end
|
12
|
+
|
13
|
+
def render_partial key, data = nil
|
14
|
+
data ||= @data
|
15
|
+
source = Source[key].first || init_source_if_file_exists(key)
|
16
|
+
source.try(:render,data)
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_content
|
20
|
+
@data[:source].try(:render,@data)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def init_source_if_file_exists key
|
26
|
+
key = Grass::Key.new(id: key)
|
27
|
+
return unless File.exists?(key.filepath)
|
28
|
+
source = Grass::Source.find_or_create_by!(filepath: key.filepath)
|
29
|
+
source.raw = File.read(source.filepath)
|
30
|
+
source.commit!
|
31
|
+
source
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/grass/key.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_support/core_ext/object/try'
|
3
|
+
|
4
|
+
module Grass
|
5
|
+
|
6
|
+
##
|
7
|
+
# Key parses path info into template meta data
|
8
|
+
|
9
|
+
class Key
|
10
|
+
|
11
|
+
include ActiveModel::Model
|
12
|
+
include ActiveModel::Validations
|
13
|
+
include ActiveModel::Validations::Callbacks
|
14
|
+
|
15
|
+
KEY_REGEX = {
|
16
|
+
locale: /\/([a-z]{2}|[a-z]{2}\-[A-Z]{2})\//,
|
17
|
+
dir: /\/(views\/layouts|views)\/|\/(scripts|stylesheets|images|fonts|statics)\/|\/(pages|texts)\//,
|
18
|
+
path: /\/([\w|\-|\/]+)/,
|
19
|
+
ext: /\.(:?[a-z]{1,})/
|
20
|
+
}
|
21
|
+
FILE_REGEX = {
|
22
|
+
# dir: /\/(views\/layouts|views)\/|\/assets\/(scripts|stylesheets|statics)\/|\/content\/(pages|texts)\//,
|
23
|
+
dir: /\/(views\/layouts|views)\/|\/(scripts|stylesheets|images|fonts|statics)\/|\/(pages|texts)\//,
|
24
|
+
path: /\/([\w|\-|\/]+)/,
|
25
|
+
locale: /\.([a-z]{2}|[a-z]{2}\-[A-Z]{2})\./,
|
26
|
+
ext: /\.(:?[a-z]{1,})/
|
27
|
+
}
|
28
|
+
|
29
|
+
ATTR_TYPES = [:id, :dir, :path, :locale, :format, :handler, :filepath]
|
30
|
+
|
31
|
+
FORMATS = {
|
32
|
+
'texts' => 'txt',
|
33
|
+
'views' => 'html',
|
34
|
+
'views/layouts' => 'html',
|
35
|
+
'pages' => 'html',
|
36
|
+
'scripts' => 'js',
|
37
|
+
'stylesheets' => 'css'
|
38
|
+
}.freeze
|
39
|
+
|
40
|
+
attr_accessor *ATTR_TYPES
|
41
|
+
|
42
|
+
validates_presence_of :id,:dir,:path,:locale,:filepath
|
43
|
+
|
44
|
+
# before_validation :parse
|
45
|
+
|
46
|
+
def initialize attributes = {}
|
47
|
+
super(attributes)
|
48
|
+
parse()
|
49
|
+
end
|
50
|
+
|
51
|
+
def serializable_hash
|
52
|
+
{id: self.id, dir: dir, path: path, locale: locale, format: format, handler: handler, filepath: filepath}
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
self.id
|
57
|
+
end
|
58
|
+
|
59
|
+
def fullpath
|
60
|
+
@fullpath ||= @dir =~ /pages/ ? "/#{@locale}/#{@dir}/#{@path}" : "/#{@locale}/#{@dir}/#{@path}.#{@format}"
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
##
|
66
|
+
# parses file or uri path to meta data
|
67
|
+
|
68
|
+
def parse
|
69
|
+
# we need one of them at least
|
70
|
+
return nil if !@id.present? && !@filepath.present?
|
71
|
+
# get key from filepath if needs
|
72
|
+
@id.nil? ? filepath_to_id : id_to_filepath
|
73
|
+
# set default locale if null
|
74
|
+
@locale ||= I18n.locale.to_s
|
75
|
+
# cleanup blanks
|
76
|
+
@handler = nil if @handler.blank?
|
77
|
+
# set default format
|
78
|
+
@format ||= FORMATS[@dir]
|
79
|
+
# rebuild key
|
80
|
+
@id = ["",@dir,@path].compact.join("/")
|
81
|
+
# rebuild filepath
|
82
|
+
dir = (is_asset? ? "assets/#{@dir}" : (is_content? ? "content/#{@dir}" : @dir))
|
83
|
+
|
84
|
+
@filepath = Grass.app_root + ["",dir,@path].compact.join("/") + ["",@locale,@format,@handler].compact.join(".")
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# parsing based on uri path
|
90
|
+
|
91
|
+
def id_to_filepath
|
92
|
+
# parse attrs
|
93
|
+
KEY_REGEX.each do |attr,regex|
|
94
|
+
if match = @id.scan(regex).try(:flatten).try(:compact)
|
95
|
+
if attr == :ext
|
96
|
+
@format = match.shift
|
97
|
+
@handler = match.join(".")
|
98
|
+
else
|
99
|
+
self.instance_variable_set "@#{attr}", match=match[0]
|
100
|
+
@id.gsub!(/\/#{match}/,"") unless match.nil?
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# parsing based on file path
|
108
|
+
|
109
|
+
def filepath_to_id
|
110
|
+
@filepath = @filepath[@filepath.index(FILE_REGEX[:dir])..-1]
|
111
|
+
FILE_REGEX.each do |attr,regex|
|
112
|
+
if match = @filepath.scan(regex).try(:flatten).try(:compact)
|
113
|
+
if attr == :ext
|
114
|
+
@format = match.shift
|
115
|
+
@handler = match.join(".")
|
116
|
+
else
|
117
|
+
self.instance_variable_set "@#{attr}", match=match[0]
|
118
|
+
unless match.nil?
|
119
|
+
attr == :locale ? @filepath.gsub!(/\.#{match}/,"") : @filepath.gsub!(/\/#{match}/,"")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
def is_asset?
|
128
|
+
@dir =~ /scripts|stylesheets|images|fonts|statics/
|
129
|
+
end
|
130
|
+
|
131
|
+
def is_content?
|
132
|
+
@dir =~ /pages|texts/
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
data/lib/grass/render.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'grass/render/text'
|
2
|
+
require 'grass/render/view'
|
3
|
+
require 'grass/render/layout'
|
4
|
+
require 'grass/render/page'
|
5
|
+
require 'grass/render/stylesheet'
|
6
|
+
require 'grass/render/script'
|
7
|
+
|
8
|
+
module Grass
|
9
|
+
|
10
|
+
module Render
|
11
|
+
|
12
|
+
def render data = {}
|
13
|
+
@data = data
|
14
|
+
result = "grass/render/#{self.type}".classify.constantize.new(self,@data).render
|
15
|
+
commit! result if self.type == "page"
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
def commit! result = nil
|
20
|
+
self.result = result || (self.type =~ /script|stylesheet/ ? self.render : self.raw)
|
21
|
+
self.save!
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'grass/render/renderer'
|
2
|
+
require 'grass/render/view'
|
3
|
+
require 'tilt/redcarpet'
|
4
|
+
|
5
|
+
module Grass
|
6
|
+
module Render
|
7
|
+
class Page
|
8
|
+
|
9
|
+
include Renderer
|
10
|
+
|
11
|
+
def render
|
12
|
+
if @data[:view].nil? && !self.view.nil?
|
13
|
+
@data[:view] = @view
|
14
|
+
@data[:source] = @source
|
15
|
+
@view.render @data
|
16
|
+
else
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def view
|
22
|
+
@view ||= begin
|
23
|
+
keys = ["/views/#{@source.dir}/#{@source.path}","/views/#{@source.dir}/#{Grass::Render::View::DEFAULT_VIEW}"]
|
24
|
+
Source[keys].first
|
25
|
+
# keys.map{|id| Source.find_by(key: Key.new(id: id))}.compact.first
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'tilt'
|
2
|
+
require 'tilt/plain'
|
3
|
+
require 'tilt/string'
|
4
|
+
require 'tilt/erb'
|
5
|
+
require 'grass/helpers/render_helper'
|
6
|
+
|
7
|
+
Tilt.register Tilt::ERBTemplate, 'html'
|
8
|
+
Tilt.register Tilt::StringTemplate, 'txt'
|
9
|
+
|
10
|
+
module Grass
|
11
|
+
module Render
|
12
|
+
module Renderer
|
13
|
+
|
14
|
+
include Grass::Helpers::RenderHelper
|
15
|
+
|
16
|
+
def initialize source, data = {}
|
17
|
+
@source = source
|
18
|
+
@data = data
|
19
|
+
end
|
20
|
+
|
21
|
+
def render
|
22
|
+
result = @source.raw
|
23
|
+
templates.each do |template|
|
24
|
+
result = template.new{result}.render(self,@data)
|
25
|
+
end
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
def templates
|
30
|
+
@templates ||= Tilt.templates_for(@source.handler || @source.format)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'grass/render/renderer'
|
2
|
+
require 'tilt/coffee'
|
3
|
+
require 'uglifier'
|
4
|
+
|
5
|
+
module Grass
|
6
|
+
module Render
|
7
|
+
class Script
|
8
|
+
|
9
|
+
JS = Uglifier.new
|
10
|
+
|
11
|
+
include Renderer
|
12
|
+
|
13
|
+
def render
|
14
|
+
compress(super)
|
15
|
+
end
|
16
|
+
|
17
|
+
def compress source
|
18
|
+
compressor.compress(source)
|
19
|
+
end
|
20
|
+
|
21
|
+
def compressor
|
22
|
+
JS
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'grass/render/renderer'
|
2
|
+
|
3
|
+
module Grass
|
4
|
+
module Render
|
5
|
+
class View
|
6
|
+
|
7
|
+
DEFAULT_LAYOUT = "application"
|
8
|
+
DEFAULT_VIEW = "show"
|
9
|
+
|
10
|
+
include Renderer
|
11
|
+
|
12
|
+
def render
|
13
|
+
if @data[:layout].nil? && !self.layout.nil?
|
14
|
+
@data[:layout] = @layout
|
15
|
+
templates.last.new{ @layout.raw }.render(self,@data) { super }
|
16
|
+
else
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def layout
|
22
|
+
@layout ||= begin
|
23
|
+
keys = ["/views/layouts/#{DEFAULT_LAYOUT}"]
|
24
|
+
unless @data[:source].nil?
|
25
|
+
keys.unshift "/views/layouts/#{@data[:source].dir}/#{@data[:source].path}",
|
26
|
+
"/views/layouts/#{@data[:source].dir}/#{DEFAULT_VIEW}"
|
27
|
+
end
|
28
|
+
Source[keys].first
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|