grass 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.sass-cache/e3d4c2039fc7a8446e752aad5ac08f85d7457f92/(__TEMPLATE__)c +0 -0
  4. data/.travis.yml +3 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +29 -0
  8. data/Rakefile +10 -0
  9. data/bin/grass +89 -0
  10. data/config/grass.rb +27 -0
  11. data/db/migrate/1_create_grass_sources.rb +24 -0
  12. data/grass.gemspec +43 -0
  13. data/lib/grass.rb +68 -0
  14. data/lib/grass/cache.rb +52 -0
  15. data/lib/grass/core_ext/kernel.rb +8 -0
  16. data/lib/grass/endpoints/api.rb +70 -0
  17. data/lib/grass/endpoints/front.rb +122 -0
  18. data/lib/grass/file_sync.rb +70 -0
  19. data/lib/grass/goliath/rack/auth_barrier.rb +109 -0
  20. data/lib/grass/goliath/rack/cache.rb +37 -0
  21. data/lib/grass/goliath/rack/cors.rb +45 -0
  22. data/lib/grass/goliath/rack/secure_headers.rb +20 -0
  23. data/lib/grass/goliath/rack/validator.rb +52 -0
  24. data/lib/grass/helpers/i18n_helper.rb +91 -0
  25. data/lib/grass/helpers/render_helper.rb +35 -0
  26. data/lib/grass/key.rb +137 -0
  27. data/lib/grass/render.rb +27 -0
  28. data/lib/grass/render/layout.rb +11 -0
  29. data/lib/grass/render/page.rb +31 -0
  30. data/lib/grass/render/renderer.rb +35 -0
  31. data/lib/grass/render/script.rb +27 -0
  32. data/lib/grass/render/stylesheet.rb +13 -0
  33. data/lib/grass/render/text.rb +11 -0
  34. data/lib/grass/render/view.rb +34 -0
  35. data/lib/grass/render/yui_renderer.rb +27 -0
  36. data/lib/grass/source.rb +107 -0
  37. data/lib/grass/tasks/db.rake +67 -0
  38. data/lib/grass/version.rb +3 -0
  39. data/lib/templates/app/Gemfile +9 -0
  40. data/lib/templates/app/Procfile +3 -0
  41. data/lib/templates/app/Rakefile +7 -0
  42. data/lib/templates/app/app/assets/scripts/application.en.js.coffee +1 -0
  43. data/lib/templates/app/app/assets/stylesheets/application.en.css.scss +4 -0
  44. data/lib/templates/app/app/content/pages/about.en.html.erb +3 -0
  45. data/lib/templates/app/app/content/pages/index.en.md.erb +5 -0
  46. data/lib/templates/app/app/views/layouts/application.en.html.erb +14 -0
  47. data/lib/templates/app/app/views/pages/show.en.html.erb +4 -0
  48. data/lib/templates/app/app/views/shared.en.html.erb +9 -0
  49. data/lib/templates/app/config/cache.yml +20 -0
  50. data/lib/templates/app/config/database.yml +35 -0
  51. data/lib/templates/app/config/grass.rb +27 -0
  52. data/lib/templates/app/db/migrate/1_create_grass_sources.rb +24 -0
  53. data/lib/templates/app/haproxy.cfg +43 -0
  54. data/lib/templates/app/public/favicon.ico +0 -0
  55. data/lib/templates/app/public/robots.txt +2 -0
  56. data/lib/templates/app/server.rb +7 -0
  57. data/test/dummy/app/content/texts/testapi.en.txt +1 -0
  58. data/test/dummy/config/cache.yml +23 -0
  59. data/test/dummy/config/database.yml +35 -0
  60. data/test/dummy/config/dummy.rb +1 -0
  61. data/test/dummy/config/haproxy.cfg +37 -0
  62. data/test/dummy/public/favicon.ico +0 -0
  63. data/test/dummy/public/robots.txt +2 -0
  64. data/test/minitest_helper.rb +38 -0
  65. data/test/support/grass.rb +21 -0
  66. data/test/support/test.jpg +0 -0
  67. data/test/test_api.rb +74 -0
  68. data/test/test_front.rb +52 -0
  69. data/test/test_key.rb +118 -0
  70. data/test/test_source.rb +51 -0
  71. data/test/test_source_file.rb +47 -0
  72. data/test/test_source_render.rb +54 -0
  73. data/vendor/yuicompressor-2.4.8.jar +0 -0
  74. 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
@@ -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
@@ -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,11 @@
1
+ require 'grass/render/renderer'
2
+
3
+ module Grass
4
+ module Render
5
+ class Layout
6
+
7
+ include Renderer
8
+
9
+ end
10
+ end
11
+ 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,13 @@
1
+ require 'grass/render/yui_renderer'
2
+ require 'tilt/sass'
3
+
4
+ module Grass
5
+ module Render
6
+ class Stylesheet
7
+
8
+ include YuiRenderer
9
+ def compressor; CSS end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ require 'grass/render/renderer'
2
+
3
+ module Grass
4
+ module Render
5
+ class Text
6
+
7
+ include Renderer
8
+
9
+ end
10
+ end
11
+ 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