pubba 0.6.0 → 0.7.0

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.
data/CHANGES CHANGED
@@ -1,5 +1,12 @@
1
1
  master
2
2
 
3
+ * Conceptual change from Sinatra extension to generic library
4
+ * Namespace changed from Sinatra::Pubba to Pubba
5
+ * Change to configuration implementation
6
+ * Pubba::Site.configure called in Pubba.configure
7
+ * Removed automatic monitoring. Detailed how to set this up as a Rake task in the README
8
+ * Added ability to set asset host(s)
9
+ * Javascript and Stylesheet folders are configurable. Defaults to 'js' and 'css'.
3
10
 
4
11
  0.6.0
5
12
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pubba
2
2
 
3
- pubba is a Sinatra extension designed to help you manage your site. It uses [Sprockets](https://github.com/sstephenson/sprockets) for packaging assets and [R18n](http://r18n.rubyforge.org/) for internationalization/localization. I use R18n as a central location for default text, the internationalization functionality is a nice bonus in the event the application needs to move in that direction.
3
+ pubba is a library designed to help you manage your site. It uses [Sprockets](https://github.com/sstephenson/sprockets) for packaging assets and [R18n](http://r18n.rubyforge.org/) for internationalization/localization. I use R18n as a central location for default text, the internationalization functionality is a nice bonus in the event the application needs to move in that direction.
4
4
 
5
5
  # Note
6
6
 
@@ -31,26 +31,45 @@ In addition, when using R18n, pubba gives you access through a single page objec
31
31
 
32
32
  More details on these later, but here are the configuration options:
33
33
 
34
- **Note: __settings.root__ refers to \<ProjectRoot\>/app/**
34
+ ### A couple of things to note:
35
35
 
36
- ### Location of the config file. REQUIRED
37
- set :pubba_config, File.join(settings.root, '..', 'config', 'pubba.yml')
36
+ * __settings.root__ refers to &lt;ProjectRoot&gt;/app/**
37
+ * Images are not processed and should be stored in your public directory
38
38
 
39
- ### Location of the public_folder. REQUIRED
40
- set :public_folder, File.join(settings.root, '..', 'public')
41
39
 
42
- ### Location of the asset_folder. REQUIRED
43
- set :asset_folder, File.join(settings.root, 'assets')
40
+ ### Sample configuration
41
+ Pubba.configure do |p|
42
+ # REQUIRED.
43
+ p.config_file = File.join( File.dirname(__FILE__), 'pubba.yml')
44
44
 
45
- ### Asset handler. Defaults to [Sprockets](https://github.com/sstephenson/sprockets)
46
- Right now there's only support for Sprockets, but leaving the option open for others.
47
- set :asset_handler, Sinatra::Pubba::Assets::SprocketsHandler
45
+ # REQUIRED.
46
+ p.asset_folder = File.join(settings.root, 'assets')
48
47
 
49
- ### Location of the [R18n](http://r18n.rubyforge.org/) folder. OPTIONAL
50
- set :r18n_folder, File.join(settings.root, 'i18n')
48
+ # REQUIRED.
49
+ p.public_folder = settings.public_folder
51
50
 
52
- ### Locale. Defaults to 'en'
53
- set :r18n_locale, 'en'
51
+ # OPTIONAL. Defaults to 'css'
52
+ p.style_folder = 'css'
53
+
54
+ # OPTIONAL. Defaults to 'js'
55
+ p.script_folder = 'js'
56
+
57
+ # OPTIONAL. Defaults to Sprockets: https://github.com/sstephenson/sprockets/
58
+ p.asset_handler = Pubba::Assets::SprocketsHandler
59
+
60
+ # OPTIONAL. Defaults to YUI Compressor: https://github.com/sstephenson/ruby-yui-compressor/
61
+ p.asset_minifier = Pubba::Assets::YUIMinifier
62
+
63
+ # OPTIONAL. Asset hosts (value must be a Proc)
64
+ p.asset_host = -> asset { [ "http://assets.mysite.com#{asset}",
65
+ "http://assets1.mysite.com#{asset}"].sample }
66
+
67
+ # OPTIONAL.
68
+ p.r18n_folder = File.join(settings.root, 'i18n')
69
+
70
+ # OPTIONAL.
71
+ p.r18n_locale, 'en'
72
+ end
54
73
 
55
74
 
56
75
 
@@ -62,17 +81,18 @@ First things first, you'll want to install the gem:
62
81
 
63
82
  Then you'll want to use it in your app like so:
64
83
 
65
- require 'sinatra/pubba'
84
+ require 'pubba'
66
85
 
67
86
  class App < Sinatra::Application
68
87
  # Settings as described above
69
- set :asset_folder, File.join(settings.root, 'assets')
70
88
  set :public_folder, File.join(settings.root, '..', 'public')
71
- set :r18n_folder, File.join(settings.root, 'i18n')
72
89
 
73
- set :pubba_config, File.join(settings.root, '..', 'config', 'pubba.yml')
74
-
75
- register Sinatra::Pubba
90
+ Pubba.configure do |p|
91
+ p.config_file = File.join(settings.root, '..', 'config', 'pubba.yml')
92
+ p.public_folder = settings.public_folder
93
+ p.asset_folder = File.join(settings.root, 'assets')
94
+ p.r18n_folder = File.join(settings.root, 'i18n')
95
+ end
76
96
  end
77
97
 
78
98
  Next up is creating the all important __pubba.yml__ config file:
@@ -168,6 +188,30 @@ Again, using the above __pubba.yml__ configuration, if you are using the 'home'
168
188
  <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js" type="text/javascript"></script>
169
189
  <script src="/js/home-body.js" type="text/javascript"></script>
170
190
 
191
+ # Monitoring changes
192
+
193
+ To monitor your assets directory to keep the public directory in sync, you'll need to create a rake task:
194
+
195
+ namespace :pubba do
196
+ desc 'Process the js and css assets then monitor for changes'
197
+ task :monitor do
198
+
199
+ # Require your app to configure Pubba
200
+ require_relative 'app/app'
201
+
202
+ require 'pubba/monitor'
203
+
204
+ # The monitor process will only run Site.process when it detects a
205
+ # change so we need to run it once to make sure the public directory
206
+ # is in sync.
207
+
208
+ puts ">> Processing assets..."
209
+ Pubba::Site.process
210
+
211
+ Pubba::Monitor.run
212
+ end
213
+ end
214
+
171
215
  # R18n
172
216
 
173
217
  If you're using R18n, you will need a translation file, here's a sample en.yml:
@@ -186,7 +230,7 @@ If you're using R18n, you will need a translation file, here's a sample en.yml:
186
230
  Take note of the __home__ section in both __pubba.yml__ and __en.yml__. If you have a route in your app that you want to use the __home__ defintions, do this:
187
231
 
188
232
  get '/' do
189
- @page = Sinatra::Pubba::Site.page('home')
233
+ @page = Pubba::Site.page('home')
190
234
  slim :"aux/index"
191
235
  end
192
236
 
@@ -203,9 +247,10 @@ Notice that `title` is defined under the `home` section, but `home_link` is a to
203
247
 
204
248
  # Acknowledgement
205
249
 
206
- Huge thanks to my company, [Primedia](http://primedia.com) for encouraging open source contributions. This particular extension is obviously very new and hasn't hit a production site yet, but it will. I will post a list here as we migrate our applications from Rails to Sinatra.
250
+ Huge thanks to my company, [Primedia](http://primedia.com) for encouraging open source contributions.
207
251
 
208
252
  # Contributors
209
253
 
210
254
  I highly value contributions and will happily list all those who submit accepted pull requests.
211
255
 
256
+ [Chas Lemley](https://github.com/chaslemley) : YUI Compressor support
@@ -0,0 +1,42 @@
1
+ require 'sinatra/base'
2
+ require 'rack/statica_server'
3
+
4
+ require_relative 'pubba/errors'
5
+ require_relative 'pubba/site'
6
+ require_relative 'pubba/html/helpers'
7
+
8
+ module Pubba
9
+ class << self
10
+ attr_accessor :config_file, :public_folder
11
+ attr_accessor :style_folder, :script_folder
12
+ attr_accessor :asset_folder, :asset_host
13
+ attr_accessor :asset_handler, :asset_minifier
14
+ attr_accessor :r18n_folder, :r18n_locale
15
+
16
+ def configure
17
+ yield self
18
+ validate_settings
19
+ Site.configure
20
+ end
21
+
22
+ def set_defaults
23
+ @asset_handler = Pubba::Assets::SprocketsHandler
24
+ @asset_minifier = Pubba::Assets::YUIMinifier
25
+ @script_folder = 'js'
26
+ @style_folder = 'css'
27
+ end
28
+
29
+ def validate_settings
30
+ missing_settings = []
31
+ missing_settings << ":public_folder has not been set!" unless Pubba.public_folder
32
+ missing_settings << ":asset_folder has not been set!" unless Pubba.asset_folder
33
+
34
+ if missing_settings.length > 0
35
+ messages = missing_settings.join("\n")
36
+ raise Pubba::ConfigurationError.new("Missing configuration options:\n#{messages}")
37
+ end
38
+ end
39
+ end
40
+ end # Pubba
41
+
42
+ Pubba.set_defaults
@@ -0,0 +1,25 @@
1
+ require 'psych'
2
+
3
+ module Pubba
4
+ module Assets
5
+ class Configuration
6
+ attr_reader :yaml, :name, :disclaimer
7
+
8
+ def initialize(config_file)
9
+ @yaml = Psych.load_file(config_file)
10
+ @name = File.basename(config_file)
11
+ @disclaimer = "// This file is automatically generated from the contents\n// in #{name}\n//\n"
12
+ end
13
+
14
+ def global_config!
15
+ @global_config ||= (yaml.delete("global") || {})
16
+ end
17
+
18
+ def process
19
+ yaml.each do |page, config|
20
+ yield page, config
21
+ end
22
+ end
23
+ end # Configuration
24
+ end # Assets
25
+ end # Pubba
@@ -0,0 +1,21 @@
1
+ module Pubba
2
+ module Assets
3
+ class Handler
4
+ def self.asset(file)
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def save_as(file)
9
+ raise NotImplementedError
10
+ end
11
+
12
+ def process(pattern, destination)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def build(name, type, ext, urls)
17
+ raise NotImplementedError
18
+ end
19
+ end # Handler
20
+ end # Assets
21
+ end # Pubba
@@ -0,0 +1,9 @@
1
+ module Pubba
2
+ module Assets
3
+ class Minifier
4
+ def self.minify(folder, handler)
5
+ raise NotImplementedError
6
+ end
7
+ end # Compressor
8
+ end # Assets
9
+ end # Pubba
@@ -0,0 +1,53 @@
1
+ require 'fileutils'
2
+ require 'sprockets'
3
+ require_relative 'handler'
4
+
5
+ module Pubba
6
+ module Assets
7
+ class SprocketsHandler < Handler
8
+ def self.find(file)
9
+ SprocketsHandler.new(sprockets.find_asset(file))
10
+ end
11
+
12
+ def self.asset_paths(*paths)
13
+ paths.each do |path|
14
+ sprockets.append_path path
15
+ end
16
+ end
17
+
18
+ def self.sprockets
19
+ @sprockets ||= Sprockets::Environment.new()
20
+ end
21
+
22
+ def self.process(source, destination)
23
+ FileUtils.mkdir_p destination
24
+
25
+ Dir.glob("#{source}/*") do |file|
26
+ asset = find(file)
27
+ asset.save_as "#{destination}/#{File.basename(file)}"
28
+ end
29
+ end
30
+
31
+ def self.build(name, type, ext, urls)
32
+ content = urls.collect{|url| "//= require #{url}.#{ext}"}.compact.join("\n")
33
+ out_folder = File.join(Pubba.asset_folder, "out", ext)
34
+ FileUtils.mkdir_p out_folder
35
+ fname = File.join(out_folder, "#{name}-#{type}.#{ext}")
36
+ File.open(fname, 'w') do |f|
37
+ f.write Site.asset_configuration.disclaimer
38
+ f.write content
39
+ end
40
+ end
41
+
42
+ attr_reader :asset
43
+
44
+ def initialize(asset)
45
+ @asset = asset
46
+ end
47
+
48
+ def save_as(file)
49
+ asset.write_to(file)
50
+ end
51
+ end # SprocketsHandler
52
+ end # Assets
53
+ end # Pubba
@@ -0,0 +1,26 @@
1
+ require "yui/compressor"
2
+ require_relative 'minifier'
3
+
4
+ module Pubba
5
+ module Assets
6
+ class YUIMinifier < Minifier
7
+ def self.minify(folder, handler)
8
+ compressor = get_compressor(handler)
9
+ Dir.glob("#{folder}/*.*") do |file|
10
+ contents = File.read(file)
11
+ File.open(file, "w") {|f| f.write(compressor.compress(contents))}
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def self.get_compressor(handler)
18
+ case handler
19
+ when :js then YUI::JavaScriptCompressor.new
20
+ when :css then YUI::CssCompressor.new
21
+ else raise ArgumentError, "minify handler must be one of [:js, :css]"
22
+ end
23
+ end
24
+ end # YUIMinifier
25
+ end # Assets
26
+ end # Pubba
@@ -0,0 +1,3 @@
1
+ module Pubba
2
+ class ConfigurationError < StandardError; end
3
+ end
@@ -0,0 +1,50 @@
1
+ module Pubba
2
+ module HTML
3
+ module Helpers
4
+ def page_head_tags
5
+ process_tags(@page.head_tags)
6
+ end
7
+
8
+ def page_body_tags
9
+ process_tags(@page.body_tags)
10
+ end
11
+
12
+ def digest_url(url)
13
+ url.start_with?('http') ? url : Site.asset_host[::Statica.digest_url(url)]
14
+ end
15
+
16
+ private
17
+
18
+ def process_tags(tags)
19
+ array = []
20
+ tags.each do |tag|
21
+ t = tag.dup
22
+ set_url(type = t.delete(:tag), t)
23
+ array << tag_content(type, '', t)
24
+ end
25
+ array.join('')
26
+ end
27
+
28
+ def set_url(type, t)
29
+ if type == 'script'
30
+ t[:src] = digest_url(t[:src])
31
+ else
32
+ t[:href] = digest_url(t[:href])
33
+ end
34
+ end
35
+
36
+ def tag_content(tag, content, attrs={}, self_closing=false)
37
+ base = "<#{tag}#{tag_attrs(attrs)}"
38
+ self_closing ? "#{base}/>":"#{base}>#{content}</#{tag}>"
39
+ end
40
+
41
+ def tag_attrs(attrs)
42
+ return '' if attrs.empty?
43
+
44
+ return " " + attrs.keys.sort.collect do |k|
45
+ %|#{k}="#{attrs[k]}"|
46
+ end.join(' ')
47
+ end
48
+ end
49
+ end # HTML
50
+ end # Pubba
@@ -0,0 +1,16 @@
1
+ require 'r18n-desktop'
2
+
3
+ module Pubba
4
+ class Locale
5
+ include R18n::Helpers
6
+
7
+ R18n.from_env Pubba.r18n_folder, Pubba.r18n_locale
8
+
9
+ def get(category, name, *args)
10
+ res = t.send(category).send(name, *args)
11
+ res = t.send(name, *args) if R18n::Untranslated === res
12
+
13
+ R18n::Untranslated === res ? nil : res
14
+ end
15
+ end # Locale
16
+ end # Pubba
@@ -0,0 +1,29 @@
1
+ require 'fssm'
2
+
3
+ module Pubba
4
+ module Monitor
5
+ extend self
6
+
7
+ def run
8
+ script_asset_folder = File.join(Pubba.asset_folder, Pubba.script_folder)
9
+ style_asset_folder = File.join(Pubba.asset_folder, Pubba.style_folder)
10
+
11
+ puts ">> Pubba is now monitoring:\n>> #{script_asset_folder}\n>> #{style_asset_folder}"
12
+ FSSM.monitor do
13
+ path script_asset_folder do
14
+ glob '**/*'
15
+ update {|base, relative, type| Site.process}
16
+ delete {|base, relative, type| Site.process}
17
+ create {|base, relative, type| Site.process}
18
+ end
19
+
20
+ path style_asset_folder do
21
+ glob '**/*'
22
+ update {|base, relative, type| Site.process}
23
+ delete {|base, relative, type| Site.process}
24
+ create {|base, relative, type| Site.process}
25
+ end
26
+ end
27
+ end
28
+ end # Monitor
29
+ end # Pubba
@@ -0,0 +1,133 @@
1
+ module Pubba
2
+ class Page
3
+ attr_accessor :name
4
+ attr_reader :assets, :head_tags, :body_tags
5
+
6
+ def initialize(name, global_configuration = {})
7
+ @name = name
8
+ @assets = {"styles" => {}, "scripts" => {}}
9
+ @head_tags = []
10
+ @body_tags = []
11
+
12
+ global_configuration["styles"].each do |key, value|
13
+ @assets["styles"][key] = value.dup
14
+ end
15
+
16
+ script_groups do |group|
17
+ @assets["scripts"][group] = global_configuration["scripts"][group] || []
18
+ end
19
+ end
20
+
21
+ def add_asset(type, section)
22
+ if type == "styles"
23
+ section.each do |section_name, hash|
24
+ hash.each do |key, value|
25
+ if key == "urls"
26
+ @assets[type][section_name][key] += value
27
+ else
28
+ @assets[type][section_name][key] = value
29
+ end
30
+ end
31
+ end
32
+ else
33
+ script_groups do |group|
34
+ @assets["scripts"][group] += section[group] if section[group]
35
+ end
36
+ end
37
+ end
38
+
39
+ def assetize
40
+ create_style_assets
41
+ create_script_assets
42
+ end
43
+
44
+ def tagify
45
+ style_groups do |group, hash|
46
+ style_urls(group) do |url|
47
+ add_style_tag(group, hash, url)
48
+ end
49
+ end
50
+
51
+ script_groups do |group|
52
+ script_urls(group) do |url|
53
+ add_script_tag(group, url)
54
+ end
55
+ end
56
+ end
57
+
58
+ def method_missing(meth, *args)
59
+ if Site.locale && (t = Site.locale.get(name, meth, *args))
60
+ return t
61
+ end
62
+ super
63
+ end
64
+
65
+ private
66
+
67
+ def process_scripts(array)
68
+ return [] if array.nil? || array.empty?
69
+
70
+ array.each{|ele| scripts << ele }
71
+ end
72
+
73
+ def create_style_assets
74
+ style_groups do |group, hash|
75
+ urls = []
76
+ style_urls(group) do |url|
77
+ next if url.start_with?("http")
78
+ urls << url
79
+ end
80
+ Pubba.asset_handler.build(name, group, "css", urls)
81
+ end
82
+ end
83
+
84
+ def create_script_assets
85
+ script_groups do |group|
86
+ urls = []
87
+ script_urls(group) do |url|
88
+ next if url.start_with?("http")
89
+ urls << url
90
+ end
91
+ Pubba.asset_handler.build(name, group, "js", urls)
92
+ end
93
+ end
94
+
95
+ def style_groups
96
+ @assets["styles"].each{|group, hash| yield group, hash }
97
+ end
98
+
99
+ def style_urls(group)
100
+ @assets["styles"][group]["urls"].each{|url| yield url }
101
+ end
102
+
103
+ def script_groups
104
+ ["head", "body"].each{|group| yield group }
105
+ end
106
+
107
+ def script_urls(group)
108
+ @assets["scripts"][group].each{|url| yield url }
109
+ end
110
+
111
+ def add_style_tag(group, hash, url)
112
+ h = { tag: 'link', type: 'text/css', rel: 'stylesheet' }
113
+ h[:media] = hash['media'] if hash['media']
114
+ h[:href] = url.start_with?("http") ? url : "/#{Pubba.style_folder}/#{name}-#{group}.css"
115
+
116
+ maybe_add_tag(@head_tags, h, :href)
117
+ end
118
+
119
+ def add_script_tag(group, url)
120
+ h = { tag: 'script', type: "text/javascript" }
121
+ h[:src] = url.start_with?("http") ? url : "/#{Pubba.script_folder}/#{name}-#{group}.js"
122
+
123
+ tag_set = (group == "head") ? @head_tags : @body_tags
124
+ maybe_add_tag(tag_set, h, :src)
125
+ end
126
+
127
+ def maybe_add_tag(tag_set, hash, key)
128
+ found = false
129
+ tag_set.each{|tag| found = true if tag[key] == hash[key]}
130
+ tag_set << hash unless found
131
+ end
132
+ end # Page
133
+ end # Pubba