limitedrun-themekit 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +41 -0
  7. data/Rakefile +11 -0
  8. data/bin/limitedrun-themekit +7 -0
  9. data/lib/limitedrun-themekit.rb +10 -0
  10. data/lib/limitedrun-themekit/config.rb +23 -0
  11. data/lib/limitedrun-themekit/renderer.rb +88 -0
  12. data/lib/limitedrun-themekit/server.rb +70 -0
  13. data/lib/limitedrun-themekit/version.rb +5 -0
  14. data/lib/liquid/filters.rb +57 -0
  15. data/lib/liquid/tags/captcha.rb +20 -0
  16. data/lib/liquid/tags/contact_form.rb +16 -0
  17. data/lib/liquid/tags/lr_include.rb +143 -0
  18. data/lib/liquid/tags/paginate.rb +16 -0
  19. data/limitedrun-themekit.gemspec +29 -0
  20. data/spec/assets/skeleton-theme/README.md +11 -0
  21. data/spec/assets/skeleton-theme/configs/default.json +314 -0
  22. data/spec/assets/skeleton-theme/javascripts/default.js +0 -0
  23. data/spec/assets/skeleton-theme/layouts/default.html +150 -0
  24. data/spec/assets/skeleton-theme/store.json +101 -0
  25. data/spec/assets/skeleton-theme/stylesheets/default.css +888 -0
  26. data/spec/assets/skeleton-theme/templates/404.html +3 -0
  27. data/spec/assets/skeleton-theme/templates/category.html +37 -0
  28. data/spec/assets/skeleton-theme/templates/contact.html +62 -0
  29. data/spec/assets/skeleton-theme/templates/event.html +121 -0
  30. data/spec/assets/skeleton-theme/templates/events.html +43 -0
  31. data/spec/assets/skeleton-theme/templates/gallery.html +13 -0
  32. data/spec/assets/skeleton-theme/templates/history.html +58 -0
  33. data/spec/assets/skeleton-theme/templates/index.html +37 -0
  34. data/spec/assets/skeleton-theme/templates/maintenance.html +31 -0
  35. data/spec/assets/skeleton-theme/templates/news-item.html +27 -0
  36. data/spec/assets/skeleton-theme/templates/news.html +37 -0
  37. data/spec/assets/skeleton-theme/templates/order.html +119 -0
  38. data/spec/assets/skeleton-theme/templates/product.html +105 -0
  39. data/spec/assets/skeleton-theme/templates/roster-item.html +136 -0
  40. data/spec/assets/skeleton-theme/templates/roster.html +41 -0
  41. data/spec/assets/skeleton-theme/templates/search.html +33 -0
  42. data/spec/features/themekit_spec.rb +22 -0
  43. data/spec/spec_helper.rb +25 -0
  44. metadata +211 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a51b3b1624ba7e219bd81a6cd9f8b30afc84e06a
4
+ data.tar.gz: 6a866143943bb7a2c8fad259df260b74d464fc0a
5
+ SHA512:
6
+ metadata.gz: 0d904f53e355e680c9bc833d8561d1e5c6f056514ba743e95db579ac64d0ed66a059ee6747596b5c85c56f707fab5667a551886fceb0dcd485cf47a46049dc87
7
+ data.tar.gz: 7772fd381af595a8079aa62d6cd9dc2cc07fee02b80ea43d7534337f60074d360ae898e3fc45744adbfeaa3c88ba74e05f8634fc6e4056eb3089c7aafad9ebba
@@ -0,0 +1,18 @@
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
18
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in limitedrun-themekit.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Howard Wilson
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.
@@ -0,0 +1,41 @@
1
+ # limitedrun-themekit
2
+
3
+ [![Build Status](http://img.shields.io/travis/watsonbox/limitedrun-themekit.svg?style=flat)](https://travis-ci.org/watsonbox/limitedrun-themekit)
4
+
5
+ Toolkit for developing [Limited Run](http://www.limitedrun.com/) themes locally. Because Limited Run themes are made up of [liquid](http://liquidmarkup.org/) templates, they can't easily be worked on offline. This gem renders the templates locally using mock data to speed up development.
6
+
7
+ Extracted from my [telescope-pinna](https://github.com/watsonbox/telescope-pinna) theme for [Pinna Records](http://www.pinnarecords.com/).
8
+
9
+ At the moment this is a work in progress but should be fairly extensible. Please open a pull request if you add functionality.
10
+
11
+
12
+ ## Installation
13
+
14
+ $ gem install limitedrun-themekit
15
+
16
+
17
+ ## Usage
18
+
19
+ Get hold of a responsive theme from Limited Run (export from the admin interface), and add a `store.json` to its root. See [store.json](https://github.com/watsonbox/limitedrun-themekit/blob/master/spec/assets/skeleton-theme/store.json) for an example as used in the specs.
20
+
21
+ Alternatively, clone [one of my themes](https://github.com/watsonbox/telescope-pinna) as a test.
22
+
23
+ $ limitedrun-themekit
24
+
25
+ This command fires up Sinatra on http://localhost:4567/ with a preview of the site.
26
+
27
+
28
+ ## Contributing
29
+
30
+ 1. Fork it
31
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
32
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
33
+ 4. Push to the branch (`git push origin my-new-feature`)
34
+ 5. Create new Pull Request
35
+
36
+
37
+ # Todo
38
+
39
+ * Implement pagination
40
+ * Add more specs
41
+ * Static site generation
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
9
+ rescue LoadError
10
+ # no rspec available
11
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'limitedrun-themekit'
4
+
5
+ Limitedrun::Themekit::Config.theme_path = ARGV[0] if ARGV[0]
6
+
7
+ Limitedrun::Themekit::Server.run!
@@ -0,0 +1,10 @@
1
+ require "limitedrun-themekit/version"
2
+ require "limitedrun-themekit/config"
3
+ require "limitedrun-themekit/renderer"
4
+ require "limitedrun-themekit/server"
5
+
6
+ module Limitedrun
7
+ module Themekit
8
+ # Your code goes here...
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ module Limitedrun
2
+ module Themekit
3
+ class Config
4
+ class << self
5
+ attr_writer :theme_path
6
+
7
+ def theme_path
8
+ @theme_path || '.'
9
+ end
10
+
11
+ DIRS = [:configs_dir, :layouts_dir, :templates_dir, :stylesheets_dir, :javascripts_dir, :snippets_dir]
12
+ attr_accessor *DIRS
13
+
14
+ # Define accessors for directories along with defaults e.g. #configs_dir -> 'configs'
15
+ DIRS.each do |dir|
16
+ define_method dir do
17
+ instance_variable_get("@#{dir}") || dir.to_s.gsub('_dir', '')
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,88 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+ require 'liquid'
3
+ require 'liquid/tags/paginate'
4
+ require 'liquid/tags/contact_form'
5
+ require 'liquid/tags/captcha'
6
+ require 'liquid/tags/lr_include'
7
+ require 'liquid/filters'
8
+ require 'hashie'
9
+ require 'json'
10
+
11
+ module Limitedrun
12
+ module Themekit
13
+ class Renderer < Struct.new(:theme_path, :request_path)
14
+ def render(template, template_assigns = nil)
15
+ assigns = global_assigns
16
+
17
+ if template_assigns
18
+ template_assigns.each_key do |k|
19
+ unless template_assigns[k].is_a?(Hashie::Mash)
20
+ template_assigns[k] = Hashie::Mash.new(instance_eval &template_assigns[k])
21
+ end
22
+ end
23
+
24
+ assigns.merge!(template_assigns.stringify_keys)
25
+ end
26
+
27
+ layout = parse_template File.join(Limitedrun::Themekit::Config.layouts_dir, 'default.html')
28
+ template = parse_template File.join(Limitedrun::Themekit::Config.templates_dir, template)
29
+
30
+ layout.render(assigns.merge('content' => template.render(assigns)))
31
+ end
32
+
33
+ def render_string(string)
34
+ Liquid::Template.parse(string).render('config' => theme_config)
35
+ end
36
+
37
+ protected
38
+
39
+ # Assignments for all liquid templates
40
+ def global_assigns
41
+ { 'config' => theme_config, 'store' => store }
42
+ end
43
+
44
+ # Theme configuration extracted from config/defaults.js and store.config overrides
45
+ def theme_config
46
+ return @theme_config if @theme_config
47
+
48
+ settings = JSON.parse(
49
+ File.read(
50
+ File.join(
51
+ Limitedrun::Themekit::Config.theme_path,
52
+ Limitedrun::Themekit::Config.configs_dir,
53
+ 'default.json'
54
+ )
55
+ )
56
+ )['settings']
57
+
58
+ config = {}
59
+
60
+ settings.each do |k, v|
61
+ config[k] = v['default'] if v['default']
62
+ end
63
+
64
+ if store && store.config
65
+ store.config.each { |k, v| config[k] = v }
66
+ end
67
+
68
+ @config = config
69
+ end
70
+
71
+ # Store mock data model loaded from store.json
72
+ def store
73
+ @store ||= Hashie::Mash.new(JSON.parse(File.read(File.join(Limitedrun::Themekit::Config.theme_path, 'store.json'))))
74
+ end
75
+
76
+ def parse_template(template)
77
+ Liquid::Template.parse(
78
+ File.read(
79
+ File.join(
80
+ Limitedrun::Themekit::Config.theme_path,
81
+ template
82
+ )
83
+ )
84
+ )
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,70 @@
1
+ require 'sinatra'
2
+
3
+ module Limitedrun
4
+ module Themekit
5
+ class Server < Sinatra::Application
6
+ get '/' do
7
+ render 'index.html'
8
+ end
9
+
10
+ get '/news' do
11
+ render 'news.html', news: Proc.new { store.news }
12
+ end
13
+
14
+ get '/news/posts/:item' do
15
+ render 'news-item.html', item: Proc.new { store.news.items.first }
16
+ end
17
+
18
+ get '/store' do
19
+ # Put all products in all categories
20
+ render 'category.html', category: Proc.new { store.categories.first.merge('products' => store.products) }
21
+ end
22
+
23
+ get '/categories/:category' do
24
+ # Put all products in all categories
25
+ render 'category.html', category: Proc.new { store.categories.find { |c| c.url == request_path }.merge('products' => store.products) }
26
+ end
27
+
28
+ get '/products/:slug' do
29
+ render 'product.html', product: Proc.new { store.products.find { |p| p.url == request_path } }
30
+ end
31
+
32
+ get '/artists' do
33
+ render 'roster.html', roster: Proc.new { store.roster }
34
+ end
35
+
36
+ get '/artists/:item' do
37
+ # Associate all products with all roster items
38
+ render 'roster-item.html', item: Proc.new {
39
+ store.roster.items.find { |i| i.url == request_path }.merge('products' => store.products)
40
+ }
41
+ end
42
+
43
+ get '/contact' do
44
+ render 'contact.html'
45
+ end
46
+
47
+ get '/stylesheets/:file' do
48
+ content_type :css
49
+
50
+ css = File.read(File.join(Config.theme_path, Config.stylesheets_dir, params[:file]))
51
+ Renderer.new(Config.theme_path).render_string(css)
52
+ end
53
+
54
+ get '/javascripts/:file' do
55
+ send_file File.join(Config.theme_path, Config.javascripts_dir, params[:file])
56
+ end
57
+
58
+ private
59
+
60
+ def render(template, assigns = nil)
61
+ # For the include tag
62
+ Liquid::Template.file_system = Liquid::LocalFileSystem.new(
63
+ File.join(Limitedrun::Themekit::Config.theme_path, Limitedrun::Themekit::Config.snippets_dir)
64
+ )
65
+
66
+ Renderer.new(Config.theme_path, request.path_info).render(template, assigns)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,5 @@
1
+ module Limitedrun
2
+ module Themekit
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,57 @@
1
+ module Liquid
2
+ module Filters
3
+ # Extracted from locomotivecms/wagon
4
+ # Write the url of a theme stylesheet
5
+ # input: name of the css file
6
+ def stylesheet_url(input)
7
+ return '' if input.nil?
8
+
9
+ if input =~ /^https?:/
10
+ input
11
+ else
12
+ input = "/stylesheets/#{input}" unless input =~ /^\//
13
+ input = "#{input}.css" unless input[-4..-1] == '.css'
14
+ input
15
+ end
16
+ end
17
+
18
+ # Extracted from locomotivecms/wagon
19
+ # Write the link to a stylesheet resource
20
+ # input: url of the css file
21
+ def stylesheet_tag(input, media = 'screen')
22
+ return '' if input.nil?
23
+
24
+ input = stylesheet_url(input)
25
+
26
+ %{<link href="#{input}" media="#{media}" rel="stylesheet" type="text/css" />}
27
+ end
28
+
29
+ def script_tag(input)
30
+ return '' if input.nil?
31
+
32
+ %{<script src="/#{Limitedrun::Themekit::Config.javascripts_dir + "/" + input}" type="text/javascript"></script>}
33
+ end
34
+
35
+ def img_tag(input, klass)
36
+ %{<img src="#{input}" class="#{klass}" />}
37
+ end
38
+
39
+ def link_to_news_item(input)
40
+ %{<a href="/news/posts/first">#{input['title']}</a>}
41
+ end
42
+
43
+ def link_to_page(input)
44
+ %{<a href="#{input['path']}">#{input['title']}</a>}
45
+ end
46
+
47
+ def link_to_category(input)
48
+ %{<a href="#{input[:url]}">#{input[:name]}</a>}
49
+ end
50
+
51
+ def link_to_roster_item(input)
52
+ %{<a href="#{input[:url]}">#{input[:name]}</a>}
53
+ end
54
+ end
55
+ end
56
+
57
+ ::Liquid::Template.register_filter(Liquid::Filters)
@@ -0,0 +1,20 @@
1
+ module Liquid
2
+ module Tags
3
+ class Captcha < Liquid::Tag
4
+ def initialize(tag_name, args, tokens)
5
+ super
6
+ end
7
+
8
+ def render(context)
9
+ %{
10
+ <script type="text/javascript">
11
+ var RecaptchaOptions = {"theme":"clean"};
12
+ </script><script type="text/javascript" src="http://www.google.com/recaptcha/api/challenge?k=6LdWsM8SAAAAAH-DqFDbCl656cx5203ZoGpZxGLs">
13
+ </script>
14
+ }
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ Liquid::Template.register_tag('captcha', Liquid::Tags::Captcha)
@@ -0,0 +1,16 @@
1
+ module Liquid
2
+ module Tags
3
+ class ContactForm < Liquid::Block
4
+ def initialize(tag_name, markup, tokens)
5
+ super
6
+ end
7
+
8
+ def render(context)
9
+ %{<form>#{super}</form>}
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ Liquid::Template.register_tag('contact_form', Liquid::Tags::ContactForm)
@@ -0,0 +1,143 @@
1
+ # encoding: UTF-8
2
+ # Extracted with a few mods from https://github.com/jekyll/jekyll/blob/master/lib/jekyll/tags/include.rb
3
+ # Liquid's default include tag does not support special chars or extensions in filenames
4
+
5
+ module Liquid
6
+ module Tags
7
+ class IncludeTagError < StandardError
8
+ attr_accessor :path
9
+
10
+ def initialize(msg, path)
11
+ super(msg)
12
+ @path = path
13
+ end
14
+ end
15
+
16
+ class Include < Liquid::Tag
17
+
18
+ SYNTAX_EXAMPLE = "{% include 'file.ext' param='value' param2='value' %}"
19
+
20
+ VALID_SYNTAX = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/
21
+ VARIABLE_SYNTAX = /(?<variable>[^{]*\{\{\s*(?<name>[\w\-\.]+)\s*(\|.*)?\}\}[^\s}]*)(?<params>.*)/
22
+
23
+ def includes_dir
24
+ File.join(File.realpath(Limitedrun::Themekit::Config.theme_path), Limitedrun::Themekit::Config.snippets_dir)
25
+ end
26
+
27
+ def initialize(tag_name, markup, tokens)
28
+ super
29
+ matched = markup.strip.match(VARIABLE_SYNTAX)
30
+ if matched
31
+ @file = matched['variable'].strip
32
+ @params = matched['params'].strip
33
+ else
34
+ @file, @params = markup.strip.split(' ', 2);
35
+ end
36
+ validate_params if @params
37
+ end
38
+
39
+ def parse_params(context)
40
+ params = {}
41
+ markup = @params
42
+
43
+ while match = VALID_SYNTAX.match(markup) do
44
+ markup = markup[match.end(0)..-1]
45
+
46
+ value = if match[2]
47
+ match[2].gsub(/\\"/, '"')
48
+ elsif match[3]
49
+ match[3].gsub(/\\'/, "'")
50
+ elsif match[4]
51
+ context[match[4]]
52
+ end
53
+
54
+ params[match[1]] = value
55
+ end
56
+ params
57
+ end
58
+
59
+ def validate_file_name(file)
60
+ if file !~ /^'[a-zA-Z0-9_\/\.-]+'$/ || file =~ /\.\// || file =~ /\/\./
61
+ raise ArgumentError.new <<-eos
62
+ Invalid syntax for include tag. File contains invalid characters or sequences:
63
+
64
+ #{file}
65
+
66
+ Valid syntax:
67
+
68
+ #{SYNTAX_EXAMPLE}
69
+
70
+ eos
71
+ end
72
+ end
73
+
74
+ def validate_params
75
+ full_valid_syntax = Regexp.compile('\A\s*(?:' + VALID_SYNTAX.to_s + '(?=\s|\z)\s*)*\z')
76
+ unless @params =~ full_valid_syntax
77
+ raise ArgumentError.new <<-eos
78
+ Invalid syntax for include tag:
79
+
80
+ #{@params}
81
+
82
+ Valid syntax:
83
+
84
+ #{SYNTAX_EXAMPLE}
85
+
86
+ eos
87
+ end
88
+ end
89
+
90
+ # Render the variable if required
91
+ def render_variable(context)
92
+ if @file.match(VARIABLE_SYNTAX)
93
+ partial = Liquid::Template.parse(@file)
94
+ partial.render!(context)
95
+ end
96
+ end
97
+
98
+ def render(context)
99
+ dir = includes_dir
100
+
101
+ file = render_variable(context) || @file
102
+ validate_file_name(file)
103
+
104
+ path = File.join(dir, file[1...-1])
105
+ validate_path(path, dir, true)
106
+
107
+ begin
108
+ partial = Liquid::Template.parse(source(path, context))
109
+
110
+ context.stack do
111
+ context['include'] = parse_params(context) if @params
112
+ partial.render!(context)
113
+ end
114
+ rescue => e
115
+ raise IncludeTagError.new e.message, File.join(INCLUDES_DIR, @file)
116
+ end
117
+ end
118
+
119
+ def validate_path(path, dir, safe)
120
+ if safe && !realpath_prefixed_with?(path, dir)
121
+ raise IOError.new "The included file '#{path}' should exist and should not be a symlink"
122
+ elsif !File.exist?(path)
123
+ raise IOError.new "Included file '#{path_relative_to_source(dir, path)}' not found"
124
+ end
125
+ end
126
+
127
+ def path_relative_to_source(dir, path)
128
+ File.join(INCLUDES_DIR, path.sub(Regexp.new("^#{dir}"), ""))
129
+ end
130
+
131
+ def realpath_prefixed_with?(path, dir)
132
+ File.exist?(path) && File.realpath(path).start_with?(dir)
133
+ end
134
+
135
+ # This method allows to modify the file content by inheriting from the class.
136
+ def source(file, context)
137
+ File.read(file)
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ Liquid::Template.register_tag('include', Liquid::Tags::Include)