grass 0.0.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.
- 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
|