client_pages 0.0.1.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +150 -0
- data/Rakefile +5 -0
- data/client_pages.gemspec +34 -0
- data/lib/client_pages/version.rb +3 -0
- data/lib/client_pages.rb +208 -0
- data/spec/fixtures/link_list.md +3 -0
- data/spec/fixtures/test_content.md +5 -0
- data/spec/fixtures/text_list.md +2 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/unit/client_pages_spec.rb +199 -0
- metadata +179 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dc3c627266b1e9187280a19b95fd613e73f08acc
|
4
|
+
data.tar.gz: 9c72b430526090a8c73866c6fef996b7e9b2e003
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e01fbb81395367d36f4217fa7221841e1881f6bedca4d0ebb9c3ebec09e3aa0687ea2f366bf5011ef429669fe72206abdcfdcea281f2101455d3a46fcd107a97
|
7
|
+
data.tar.gz: e471af6747143523d2c94fd5de271d981f16f4011dd1faff736b2028e8e6625414a3f43edfabfd88efa754b901cf68b47fb7321ca77238d83c39998b6d47bd47
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Marco Schaden
|
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,150 @@
|
|
1
|
+
# ClientPages
|
2
|
+
|
3
|
+
##### Todo:
|
4
|
+
|
5
|
+
- fallback option? => error handling if no such file?
|
6
|
+
- yaml content?
|
7
|
+
- builder#wrap needs conditional and multi wrap + nested wrap
|
8
|
+
- builder methoden vereinheitlichen
|
9
|
+
- builder defaults? (all :p will get class x)
|
10
|
+
- > maybe adjust ```render_content``` with additional parameter, like simple_form does
|
11
|
+
- > defaults: :table_settings => will be stored somewhere as a block
|
12
|
+
- test new configs! (.remote, .remote_content_path, .content_path, .caching, .fallback, .i18n)
|
13
|
+
- railtie
|
14
|
+
- i18n support content/de/something, content/en/something
|
15
|
+
- extract classes
|
16
|
+
- oauth dropbox?
|
17
|
+
- logger!?
|
18
|
+
- back up command/rake task, content:pull
|
19
|
+
nimmt alle local content files und überschreibt diese mit denen der remote version (wenn verfügbar)
|
20
|
+
- travis
|
21
|
+
- codeclimate
|
22
|
+
|
23
|
+
---
|
24
|
+
|
25
|
+
# Ideas/Notes:
|
26
|
+
|
27
|
+
---
|
28
|
+
|
29
|
+
with client_pages you can separate your content from html markup and css styles.
|
30
|
+
|
31
|
+
use it for small content parts of your app which need to be updated regulary by someone
|
32
|
+
or outsource all your content together, it's up to you.
|
33
|
+
|
34
|
+
**Ever heard "can we change the text on page x?"**
|
35
|
+
|
36
|
+
the main benefit of doing this: anybody can change the content, without the need for an admin backend and stuff like this.
|
37
|
+
just make it possible for them to edit the files.
|
38
|
+
|
39
|
+
from the beginning client pages was built for supporting remote files.
|
40
|
+
an easy example could be a public shared dropbox folder (or anything that can deliver files),
|
41
|
+
|
42
|
+
a sample configuration could look like this:
|
43
|
+
here we set ```bla = true``` to enable ...
|
44
|
+
|
45
|
+
btw, the core markdown parser is redcarpet and you can customize the behaviour
|
46
|
+
of it like you would normaly do by setting renderer and extensions through the config. (~ naaa, formulierung)
|
47
|
+
|
48
|
+
but the power of client_pages comes from its helper methods for rendering content
|
49
|
+
|
50
|
+
### simple example
|
51
|
+
|
52
|
+
imaginge you have this markdown file:
|
53
|
+
**insert markdown here**
|
54
|
+
|
55
|
+
Let's say you want a class on each list item, and a data attribute on that link.
|
56
|
+
And maybe a 'active' class on any link, when it's the current path?
|
57
|
+
It would also be nice to wrap each paragraph in a container?
|
58
|
+
|
59
|
+
we can accomplish all that by just giving the ```render_content(:file)``` method a block
|
60
|
+
and tell the content builder to do that for you.
|
61
|
+
**insert haml here**
|
62
|
+
|
63
|
+
this way, you can add a lot of behaivour by leaving your markdown content just as content with raw structure,
|
64
|
+
and the main markup and styling stays in your views.
|
65
|
+
|
66
|
+
### Installation and setup
|
67
|
+
|
68
|
+
Add this line to your application's Gemfile:
|
69
|
+
|
70
|
+
gem 'client_pages'
|
71
|
+
|
72
|
+
And then execute:
|
73
|
+
|
74
|
+
$ bundle
|
75
|
+
|
76
|
+
Or install it yourself as:
|
77
|
+
|
78
|
+
$ gem install client_pages
|
79
|
+
|
80
|
+
rails project?
|
81
|
+
|
82
|
+
add a content directory and your done, per default it's _content_
|
83
|
+
maybe you like to configure this to app/content or app/views/content
|
84
|
+
totaly up to you.
|
85
|
+
|
86
|
+
no framework? pure rack? sinatra?
|
87
|
+
|
88
|
+
for convenience you can add ``` include ClientPages::MarkdownContent ``` to your application controller/view_helper/presenter/... or something which is responsible for building html
|
89
|
+
so that you can call ```render_content``` there.
|
90
|
+
|
91
|
+
or if you're riding the real blank slate, just call ```ClientPages::MarkdownContent.render_content``` directly.
|
92
|
+
|
93
|
+
### renderer
|
94
|
+
if you call ```render_content``` without a block it just renders the markdown to html
|
95
|
+
|
96
|
+
```render_content(:index)```
|
97
|
+
|
98
|
+
```render_content('posts/index')```
|
99
|
+
|
100
|
+
### builder
|
101
|
+
- modify
|
102
|
+
- wrap
|
103
|
+
- switch?
|
104
|
+
|
105
|
+
### matcher
|
106
|
+
- implicit attribute match
|
107
|
+
- explicit match
|
108
|
+
- content match? (hole file?)
|
109
|
+
|
110
|
+
### config
|
111
|
+
config.ru file or initializer
|
112
|
+
|
113
|
+
- by default...
|
114
|
+
- content_path
|
115
|
+
- if you go the full remote path without fallback, you might consider to turn on caching.
|
116
|
+
|
117
|
+
### i18n
|
118
|
+
per default client_pages looks in your content_path for /locale/...
|
119
|
+
- is the locale a config_setting?
|
120
|
+
- how to tell client_pages the locale?
|
121
|
+
|
122
|
+
hm, or where is the problem? ```render_content("#{my_locale}/posts/index")```
|
123
|
+
|
124
|
+
### caching
|
125
|
+
the current implementation it the easiest way to do this.
|
126
|
+
it's really simple and lives as long as the process runs.
|
127
|
+
it can not be shared between processes
|
128
|
+
and because one of the [hard things in Computer Science](http://martinfowler.com/bliki/TwoHardThings.html) is _cache invalidation_
|
129
|
+
you can flush the cache also at runtime either complete or by passing a file_path (which is the cache key):
|
130
|
+
|
131
|
+
you can accomplish this with some additional code.
|
132
|
+
|
133
|
+
```code follows```
|
134
|
+
|
135
|
+
or run the corresponding command. (not implemented yet)
|
136
|
+
|
137
|
+
### commands
|
138
|
+
- downloader!!!
|
139
|
+
- cache killer?
|
140
|
+
|
141
|
+
----
|
142
|
+
|
143
|
+
## Contributing
|
144
|
+
|
145
|
+
+ Fork it
|
146
|
+
+ Change it
|
147
|
+
+ Test it
|
148
|
+
+ Commit it
|
149
|
+
+ Push it
|
150
|
+
+ Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'client_pages/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'client_pages'
|
8
|
+
s.version = ClientPages::VERSION
|
9
|
+
s.authors = ['Marco Schaden']
|
10
|
+
s.email = ['ms@donschado.de']
|
11
|
+
s.summary = %q{With client_pages you can separate your content from html markup and css styles}
|
12
|
+
s.description = <<-EOF
|
13
|
+
With client_pages you can separate your content from html markup and css styles.
|
14
|
+
use it for small content parts of your app which need to be updated regulary by someone
|
15
|
+
or outsource all your content together, it's up to you.
|
16
|
+
EOF
|
17
|
+
s.homepage = ''
|
18
|
+
s.license = 'MIT'
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split($/)
|
21
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
23
|
+
s.require_paths = ['lib']
|
24
|
+
|
25
|
+
s.add_dependency 'redcarpet', '~> 3.1.1'
|
26
|
+
|
27
|
+
s.add_development_dependency 'bundler', '~> 1.5.3'
|
28
|
+
s.add_development_dependency 'rake', '~> 10.1.1'
|
29
|
+
s.add_development_dependency 'rspec', '~> 2.14.1'
|
30
|
+
s.add_development_dependency 'webmock', '~> 1.17.3'
|
31
|
+
s.add_development_dependency 'pry-plus', '~> 1.0.0'
|
32
|
+
s.add_development_dependency 'rubocop', '~> 0.18.1'
|
33
|
+
s.add_development_dependency 'simplecov', '~> 0.8.2'
|
34
|
+
end
|
data/lib/client_pages.rb
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'client_pages/version'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'redcarpet'
|
5
|
+
|
6
|
+
module ClientPages
|
7
|
+
class << self
|
8
|
+
attr_accessor :configuration
|
9
|
+
|
10
|
+
def config
|
11
|
+
self.configuration ||= Configuration.new
|
12
|
+
block_given? ? yield(configuration) : configuration
|
13
|
+
end
|
14
|
+
|
15
|
+
def cache
|
16
|
+
@cache ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def clear_cache(key=nil)
|
20
|
+
if key
|
21
|
+
cache.delete(key)
|
22
|
+
"cache cleared: #{key}"
|
23
|
+
else
|
24
|
+
cache.clear
|
25
|
+
'cache cleared'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module MarkdownContent
|
31
|
+
class Matcher
|
32
|
+
attr_accessor :tag, :line
|
33
|
+
|
34
|
+
def initialize(tag, line)
|
35
|
+
@tag, @line = tag, line
|
36
|
+
end
|
37
|
+
|
38
|
+
def exact_match?(attribute, match)
|
39
|
+
!!line.match(/<#{tag}[^>]*#{attribute}="#{match}"/i)
|
40
|
+
end
|
41
|
+
|
42
|
+
def attr?(attribute, match)
|
43
|
+
!!line.match(/#{attribute}="#{match}"/i)
|
44
|
+
end
|
45
|
+
|
46
|
+
def match?(pattern)
|
47
|
+
!!line.match(pattern)
|
48
|
+
end
|
49
|
+
|
50
|
+
def find(type)
|
51
|
+
pattern = { opening: /<#{tag}[^>]*>/, closing: /<\/#{tag}[^>]*>/ }[type]
|
52
|
+
if block_given? && !!line.match(pattern)
|
53
|
+
line.gsub(pattern) { |match| yield match }
|
54
|
+
else
|
55
|
+
line
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class ContentBuilder
|
61
|
+
attr_accessor :html
|
62
|
+
|
63
|
+
def initialize(html)
|
64
|
+
@html = html
|
65
|
+
end
|
66
|
+
|
67
|
+
def modify(tag, options={}, conditions=true)
|
68
|
+
if conditions.respond_to? :call
|
69
|
+
html_lines do |line|
|
70
|
+
if conditions.call(Matcher.new(tag, line))
|
71
|
+
set_options_for_tags_per_line(options, tag, line)
|
72
|
+
else
|
73
|
+
line
|
74
|
+
end
|
75
|
+
end
|
76
|
+
elsif conditions.is_a?(Hash)
|
77
|
+
html_lines do |line|
|
78
|
+
if conditions.keys.all? { |key| Matcher.new(tag, line).exact_match?(key, conditions[key]) }
|
79
|
+
set_options_for_tags_per_line(options, tag, line)
|
80
|
+
else
|
81
|
+
line
|
82
|
+
end
|
83
|
+
end
|
84
|
+
elsif !!conditions
|
85
|
+
set_options_for_tags(options, tag)
|
86
|
+
end
|
87
|
+
self.html
|
88
|
+
end
|
89
|
+
|
90
|
+
def wrap(tag, options={})
|
91
|
+
html_lines do |line|
|
92
|
+
line = Matcher.new(tag, line).find(:opening) do |match|
|
93
|
+
"<#{options[:wrapper]} class=\"#{options[:class]}\">#{match}"
|
94
|
+
end
|
95
|
+
line = Matcher.new(tag, line).find(:closing) do |match|
|
96
|
+
"#{match}</#{options[:wrapper]}>"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def replace(tag, new_tag, options={})
|
102
|
+
html_lines do |line|
|
103
|
+
line = Matcher.new(tag, line).find(:opening) do |match|
|
104
|
+
"<#{new_tag} class=\"#{options[:class]}\">"
|
105
|
+
end
|
106
|
+
line = Matcher.new(tag, line).find(:closing) do |match|
|
107
|
+
"</#{new_tag}>"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end # ContentBuilder
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def html_lines(&block)
|
115
|
+
self.html = html.lines.map { |line| yield line }.join
|
116
|
+
end
|
117
|
+
|
118
|
+
def set_options_for_tags(options, tag)
|
119
|
+
html_lines { |line| set_options_for_tags_per_line(options, tag, line) }
|
120
|
+
end
|
121
|
+
|
122
|
+
def set_options_for_tags_per_line(options, tag, line)
|
123
|
+
options.each { |k, v| line = line.gsub(/<#{tag}/, "<#{tag} #{k}=\"#{v}\"") }
|
124
|
+
line
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def render_content(file, options = markdown_extensions)
|
129
|
+
html = Redcarpet::Markdown.new(html_renderer, options).render(markdown(file))
|
130
|
+
if block_given?
|
131
|
+
yield(ContentBuilder.new(html))
|
132
|
+
else
|
133
|
+
html.strip
|
134
|
+
end
|
135
|
+
rescue OpenURI::HTTPError
|
136
|
+
"404: could not find '#{file}'"
|
137
|
+
rescue SocketError
|
138
|
+
'Error: content_path not available'
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def html_renderer
|
144
|
+
Redcarpet::Render::HTML.new(config.render_options)
|
145
|
+
end
|
146
|
+
|
147
|
+
def markdown_extensions
|
148
|
+
config.markdown_extensions
|
149
|
+
end
|
150
|
+
|
151
|
+
def markdown(file)
|
152
|
+
url = "#{content_path}/#{file}.md"
|
153
|
+
if caching?
|
154
|
+
cache.fetch(url) { cache[url] = open(url).read }
|
155
|
+
else
|
156
|
+
open(url).read
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def config
|
161
|
+
ClientPages.config
|
162
|
+
end
|
163
|
+
|
164
|
+
def cache
|
165
|
+
ClientPages.cache
|
166
|
+
end
|
167
|
+
|
168
|
+
def caching?
|
169
|
+
config.caching
|
170
|
+
end
|
171
|
+
|
172
|
+
def remote?
|
173
|
+
config.remote
|
174
|
+
end
|
175
|
+
|
176
|
+
def content_path
|
177
|
+
remote? ? config.remote_content_path : config.content_path
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class Configuration
|
182
|
+
attr_accessor :content_path, :remote, :remote_content_path, :caching, :fallback, :i18n
|
183
|
+
attr_reader :render_options, :markdown_extensions
|
184
|
+
|
185
|
+
def initialize
|
186
|
+
@content_path = 'content'
|
187
|
+
@render_options = {}
|
188
|
+
@markdown_extensions = {}
|
189
|
+
end
|
190
|
+
|
191
|
+
def render_options=(options)
|
192
|
+
@render_options.merge!(options)
|
193
|
+
end
|
194
|
+
|
195
|
+
def markdown_extensions=(options)
|
196
|
+
@markdown_extensions.merge!(options)
|
197
|
+
end
|
198
|
+
|
199
|
+
def to_hash
|
200
|
+
# TODO: instance vars get
|
201
|
+
{
|
202
|
+
content_path: content_path,
|
203
|
+
render_options: render_options,
|
204
|
+
markdown_extensions: markdown_extensions
|
205
|
+
}
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter '/spec/'
|
4
|
+
end
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
8
|
+
require 'client_pages'
|
9
|
+
|
10
|
+
require 'webmock/rspec'
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
14
|
+
config.run_all_when_everything_filtered = true
|
15
|
+
config.filter_run :focus
|
16
|
+
config.order = 'random' # --seed 1234
|
17
|
+
|
18
|
+
config.expect_with :rspec do |c|
|
19
|
+
c.syntax = :expect
|
20
|
+
end
|
21
|
+
|
22
|
+
config.mock_with :rspec do |c|
|
23
|
+
c.syntax = :expect
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# helper for markdown fixture files
|
28
|
+
def fixture(name)
|
29
|
+
File.open(File.expand_path("spec/fixtures/#{name}.md"), 'r')
|
30
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ClientPages
|
4
|
+
describe MarkdownContent do
|
5
|
+
describe 'Renderer' do
|
6
|
+
subject { Class.send(:include, ClientPages::MarkdownContent).new }
|
7
|
+
let(:markdown) { fixture('test_content') }
|
8
|
+
let(:content) { subject.render_content(:test_content) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
ClientPages.config do |c|
|
12
|
+
c.remote = true
|
13
|
+
c.caching = false
|
14
|
+
c.remote_content_path = 'http://somewhere.com'
|
15
|
+
end
|
16
|
+
stub_request(:get, 'http://somewhere.com/test_content.md').
|
17
|
+
to_return(body: markdown, status: 200)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'renders HTML content for given markdown file from somewhere' do
|
21
|
+
expect(content).to include('<h1>Content from <a href="http://somewhere.com">somewhere</a></h1>')
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with a block' do
|
25
|
+
let(:link_list) { fixture('link_list') }
|
26
|
+
let(:text_list) { fixture('text_list') }
|
27
|
+
before do
|
28
|
+
stub_request(:get, 'http://somewhere.com/link_list.md').to_return(body: link_list, status: 200)
|
29
|
+
stub_request(:get, 'http://somewhere.com/text_list.md').to_return(body: text_list, status: 200)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'returns a content builder' do
|
33
|
+
expect(
|
34
|
+
subject.render_content(:test_content) { |c| c }
|
35
|
+
).to be_kind_of(MarkdownContent::ContentBuilder)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'modifies tags and html attributes' do
|
39
|
+
expect(
|
40
|
+
subject.render_content(:test_content) do |c|
|
41
|
+
c.modify :h1, class: 'mycss_class', 'data-foo' => 'bar'
|
42
|
+
c.modify :a, class: 'fancy', title: 'woah'
|
43
|
+
end
|
44
|
+
).to include('<h1 data-foo="bar" class="mycss_class">Content from <a title="woah" class="fancy" href="http://somewhere.com">somewhere</a></h1>')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'adds css classes conditionally' do
|
48
|
+
expect(
|
49
|
+
subject.render_content(:test_content) do |c|
|
50
|
+
c.modify :h1, { class: 'add_me cool' }, true
|
51
|
+
c.modify :a, { class: 'add_me_not' }, false
|
52
|
+
end
|
53
|
+
).to include('<h1 class="add_me cool">Content from <a href="http://somewhere.com">somewhere</a></h1>')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'adds any attribute conditionally and selective' do
|
57
|
+
expect(
|
58
|
+
subject.render_content(:link_list) do |c|
|
59
|
+
c.modify :li, { class: 'yay' }, ->(m) { m.attr?(:href, '/link1') }
|
60
|
+
c.modify :a, { class: 'active' }, href: '/link2'
|
61
|
+
c.modify :a, { class: 'important' }, title: 'i haz title'
|
62
|
+
end
|
63
|
+
).to include("<li class=\"yay\"><a href=\"/link1\">link1</a></li>\n<li><a class=\"active\" href=\"/link2\">link2</a></li>\n<li><a class=\"important\" href=\"/link3\" title=\"i haz title\">link3</a></li>")
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'wraps multiple tags with container' do
|
67
|
+
expect(
|
68
|
+
subject.render_content(:text_list) do |c|
|
69
|
+
c.wrap :li, wrapper: :span, class: 'wrapper'
|
70
|
+
c.wrap :em, wrapper: :span, class: 'special'
|
71
|
+
end
|
72
|
+
).to eql("<ul>\n<span class=\"wrapper\"><li>Foo <span class=\"special\"><em>Bar</em></span></li></span>\n<span class=\"wrapper\"><li>Baz</li></span>\n</ul>\n")
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'wraps and modifies tags' do
|
76
|
+
expect(
|
77
|
+
subject.render_content(:text_list) do |c|
|
78
|
+
c.wrap :ul, wrapper: :div, class: 'container'
|
79
|
+
c.modify :em, style: 'color: #c0ffee'
|
80
|
+
end
|
81
|
+
).to eql("<div class=\"container\"><ul>\n<li>Foo <em style=\"color: #c0ffee\">Bar</em></li>\n<li>Baz</li>\n</ul></div>\n")
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'replaces tags and adds class' do
|
85
|
+
expect(
|
86
|
+
subject.render_content(:test_content) do |c|
|
87
|
+
c.replace :p, :div, class: 'byebye'
|
88
|
+
c.replace :em, :span, class: 'cooler'
|
89
|
+
end
|
90
|
+
).to eql("<h1>Content from <a href=\"http://somewhere.com\">somewhere</a></h1>\n\n<div class=\"byebye\">Lorem do <span class=\"cooler\">incididunt</span> ut labore</div>\n\n<div class=\"byebye\">Pariatur: <a href=\"/bar.com\" title=\"baz\">Foo</a> culpa</div>\n")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "caching" do
|
95
|
+
before do
|
96
|
+
ClientPages.config do |c|
|
97
|
+
c.caching = true
|
98
|
+
c.remote = true
|
99
|
+
c.remote_content_path = "http://c.ly"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
after { ClientPages.config { |c| c.caching = false } }
|
103
|
+
before(:each) { ClientPages.clear_cache }
|
104
|
+
|
105
|
+
it 'populates cache when empty' do
|
106
|
+
request = stub_request(:get, %r[/caching.md]).to_return(body: '**cache me**')
|
107
|
+
expect { subject.render_content(:caching) }.to change { ClientPages.cache }.to eql({"http://c.ly/caching.md"=>"**cache me**"})
|
108
|
+
expect(request).to have_been_made.times(1)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'makes request only once' do
|
112
|
+
request = stub_request(:get, %r[/caching.md]).to_return(body: '**cache me**')
|
113
|
+
5.times { subject.render_content(:caching) }
|
114
|
+
expect { subject.render_content(:caching) }.to_not change { ClientPages.cache }
|
115
|
+
expect(request).to have_been_made.times(1)
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'disabled' do
|
119
|
+
before do
|
120
|
+
ClientPages.config do |c|
|
121
|
+
c.caching = false
|
122
|
+
c.remote = true
|
123
|
+
c.remote_content_path = "http://c.ly"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'will not cache' do
|
128
|
+
stub_request(:get, %r[/no_caching.md]).to_return(body: '_u_no_cache_me_')
|
129
|
+
subject.render_content(:no_caching)
|
130
|
+
expect(ClientPages.cache).to be_empty
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'makes a request per render_content call' do
|
134
|
+
request = stub_request(:get, %r[/no_caching.md]).to_return(body: '_u_no_cache_me_')
|
135
|
+
5.times { subject.render_content(:no_caching) }
|
136
|
+
expect(request).to have_been_made.times(5)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe 'configuration' do
|
143
|
+
subject { ClientPages }
|
144
|
+
before { subject.configuration = nil }
|
145
|
+
|
146
|
+
it 'has default config' do
|
147
|
+
config = {
|
148
|
+
content_path: 'content',
|
149
|
+
render_options: {},
|
150
|
+
markdown_extensions: {}
|
151
|
+
}
|
152
|
+
expect(subject.config.to_hash).to eql(config)
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'is configurable' do
|
156
|
+
config = {
|
157
|
+
content_path: 'somewhere',
|
158
|
+
render_options: { foo: "bar" },
|
159
|
+
markdown_extensions: { bam: true }
|
160
|
+
}
|
161
|
+
expect {
|
162
|
+
subject.config do |c|
|
163
|
+
c.content_path = 'somewhere'
|
164
|
+
c.render_options = { foo: 'bar' }
|
165
|
+
c.markdown_extensions = { bam: true }
|
166
|
+
end
|
167
|
+
}.to change {
|
168
|
+
subject.config.to_hash
|
169
|
+
}.to eql(config)
|
170
|
+
end
|
171
|
+
|
172
|
+
describe 'overwritable config' do
|
173
|
+
before do
|
174
|
+
subject.config do |c|
|
175
|
+
c.render_options = { opt1: true }
|
176
|
+
c.markdown_extensions = { ext1: true }
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'overwrites config and keeps untouched config settings' do
|
181
|
+
config = {
|
182
|
+
content_path: 'here',
|
183
|
+
render_options: { opt1: false, opt2: true },
|
184
|
+
markdown_extensions: { ext1: true, ext2: false }
|
185
|
+
}
|
186
|
+
expect {
|
187
|
+
subject.config do |c|
|
188
|
+
c.content_path = 'here'
|
189
|
+
c.render_options = { opt1: false, opt2: true }
|
190
|
+
c.markdown_extensions = { ext2: false }
|
191
|
+
end
|
192
|
+
}.to change {
|
193
|
+
subject.config.to_hash
|
194
|
+
}.to eql(config)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
metadata
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: client_pages
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.alpha
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marco Schaden
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redcarpet
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.1.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.1.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.5.3
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.5.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 10.1.1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 10.1.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.14.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.14.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: webmock
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.17.3
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.17.3
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry-plus
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.0.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.0.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.18.1
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.18.1
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.8.2
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.8.2
|
125
|
+
description: |2
|
126
|
+
With client_pages you can separate your content from html markup and css styles.
|
127
|
+
use it for small content parts of your app which need to be updated regulary by someone
|
128
|
+
or outsource all your content together, it's up to you.
|
129
|
+
email:
|
130
|
+
- ms@donschado.de
|
131
|
+
executables: []
|
132
|
+
extensions: []
|
133
|
+
extra_rdoc_files: []
|
134
|
+
files:
|
135
|
+
- .gitignore
|
136
|
+
- Gemfile
|
137
|
+
- LICENSE.txt
|
138
|
+
- README.md
|
139
|
+
- Rakefile
|
140
|
+
- client_pages.gemspec
|
141
|
+
- lib/client_pages.rb
|
142
|
+
- lib/client_pages/version.rb
|
143
|
+
- spec/fixtures/link_list.md
|
144
|
+
- spec/fixtures/test_content.md
|
145
|
+
- spec/fixtures/text_list.md
|
146
|
+
- spec/spec_helper.rb
|
147
|
+
- spec/unit/client_pages_spec.rb
|
148
|
+
homepage: ''
|
149
|
+
licenses:
|
150
|
+
- MIT
|
151
|
+
metadata: {}
|
152
|
+
post_install_message:
|
153
|
+
rdoc_options: []
|
154
|
+
require_paths:
|
155
|
+
- lib
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - '>='
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - '>'
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: 1.3.1
|
166
|
+
requirements: []
|
167
|
+
rubyforge_project:
|
168
|
+
rubygems_version: 2.1.11
|
169
|
+
signing_key:
|
170
|
+
specification_version: 4
|
171
|
+
summary: With client_pages you can separate your content from html markup and css
|
172
|
+
styles
|
173
|
+
test_files:
|
174
|
+
- spec/fixtures/link_list.md
|
175
|
+
- spec/fixtures/test_content.md
|
176
|
+
- spec/fixtures/text_list.md
|
177
|
+
- spec/spec_helper.rb
|
178
|
+
- spec/unit/client_pages_spec.rb
|
179
|
+
has_rdoc:
|