fuse 0.1.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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZDNiOTM4NDIyZTA2ZjNkYWQ5MmViMGEwNzZhM2JkZmE1OWYxMGI4MA==
5
+ data.tar.gz: !binary |-
6
+ YWY3MmY3YzEyOTY1ZDNhMDIwZTAzMzExN2FlYTMyZDY0NGJjYmYyNA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZmMwNzExMGExNjVmZWEyYjY3ZmM2OTNiYzgwYTcxZGFjY2YzZDdkZTM5MGRj
10
+ ODI4ZTdmMzBhNDM0OWZkNTQ1M2ZkMDFiZTIzZjkyYTQwNTQwOTdjZmIwYmEz
11
+ NzlkNGZmMDUwYmM1NDQ3MGY3YmMzNTIyZjExYWQzNWY1Yzc5MDI=
12
+ data.tar.gz: !binary |-
13
+ MzJmNDEwOGIzYTBiNGVkMDNjYTk2Y2RlNDIxNWVjNjBhYzZkZjcyNjVkN2M1
14
+ ODg2YTM1Njc2YjNlNWI4NzkyMTcxNjMzM2E2Y2EzZWYyMmU1NGYxOTQyYjc5
15
+ YjU4NDE3YjY1NjVkNGI4NTdkMDVkOGYxY2IyMTA0OTk2ZGFkMDQ=
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2013 Neil E. Pearson
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # Fuse
2
+
3
+ Portable document authoring. Fuse HTML, JavaScript, CSS, images and fonts into standalone HTML files.
4
+
5
+ * Drop all your assets in a directory and hit the magic button.
6
+ * Built-in web server based on Thin for authoring your docs.
7
+ * Support for SASS and CoffeeScript.
8
+ * Sprockets-like 'require' syntax for JS and CSS dependency
9
+ * Uses uglify-js to compress JavaScript
10
+ * Simple file naming conventions for font names and CSS media types
11
+ * Transform XML documents on the fly using XSLT
12
+
13
+ ## How to use Fuse
14
+
15
+ ### Authoring
16
+
17
+ Put some HTML, CSS, JavaScript etc in a directory. `cd` to that directory and run:
18
+
19
+ ```bash
20
+ fuse server
21
+ ```
22
+
23
+ Go to `http://localhost:9460` to view your doc.
24
+
25
+ ### Compiling
26
+
27
+ When you're happy, from the same directory, run:
28
+
29
+ ```bash
30
+ fuse compile > my_doc.html
31
+ ```
32
+
33
+ Presto.
34
+
35
+ ## Command Line Options
36
+
37
+ Run `fuse` for a full list of command line options.
38
+
39
+ Some things you can do from the command line:
40
+
41
+ * Specify a port for the server
42
+ * Enable/disable asset embedding and/or compression
43
+ * Specify a source document and/or XSL stylesheet
44
+ * Specify the output HTML document's character set
45
+ * Specify an HTML title
46
+
47
+ ## Early days
48
+
49
+ This gem is truly in its infancy. I'll put in what time I have available. It's also my first gem, so I welcome suggestions, pull requests etc.
50
+
51
+ ## License
52
+
53
+ Released under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). See [LICENSE](LICENSE) for details.
data/bin/fuse ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path '../../lib/fuse', __FILE__
3
+ Fuse.main
data/lib/fuse.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'optparse'
2
+ require 'thin'
3
+
4
+ module Fuse
5
+
6
+ VERSION = '0.1.1'
7
+
8
+ def self.root
9
+ @root ||= File.expand_path File.dirname(__FILE__)
10
+ end
11
+
12
+ end
13
+
14
+ $:.unshift Fuse.root unless $:.include?(Fuse.root)
15
+
16
+ require 'fuse/main'
17
+ require 'fuse/exceptions'
18
+ require 'fuse/server'
19
+ require 'fuse/document'
20
+ require 'fuse/document/asset'
21
+ require 'fuse/document/asset_types'
22
+ require 'fuse/document/asset_collection'
23
+
24
+ Fuse.main if $0 == __FILE__
@@ -0,0 +1,140 @@
1
+ require 'nokogiri'
2
+
3
+ class Fuse::Document
4
+
5
+ attr_reader :source_path, :xsl_path
6
+
7
+ def initialize(options)
8
+ source = (@options = options)[:source]
9
+ raise Fuse::Exception::SourceUnknown if source.nil?
10
+ raise Fuse::Exception::SourceUnknown::NotFound.new(source) unless File.exists?(source)
11
+ @source_path = expect_one(potential_sources, :source, Fuse::Exception::SourceUnknown)
12
+ @xsl_path = expect_one(potential_xsl, :xsl, Fuse::Exception::XslMissing) if source_xml?
13
+ end
14
+
15
+ def to_s
16
+ result.to_html encoding: @options[:encoding]
17
+ end
18
+
19
+ def result
20
+
21
+ #todo find a way to transform Nokogiri XML to HTML without serializing
22
+
23
+ document = if xsl_path
24
+ Nokogiri::HTML(Nokogiri::XSLT(File.read xsl_path).transform(Nokogiri::XML(File.read source_path)).to_html encoding: @options[:encoding])
25
+ else
26
+ Nokogiri::HTML(File.read source_path)
27
+ end
28
+
29
+ html = document.css('> html').first
30
+ body = html.css('> body').first
31
+ head = html.css('> head').first || body.add_previous_sibling(Nokogiri::XML::Node.new 'head', document)
32
+
33
+ document.title = @options[:title] unless @options[:title].nil?
34
+
35
+ #attach stylesheets and scripts
36
+ [Fuse::Document::Asset::StyleSheet, Fuse::Document::Asset::JavaScript].each do |klass|
37
+ collection = assets.of_type(klass).sort!
38
+ next unless collection.length > 0
39
+ if @options[:embed_assets]
40
+ tag = Nokogiri::XML::Node.new(klass::EMBED_WITH, document)
41
+ raw = collection.map do |asset|
42
+ tag['type'] = asset.type
43
+ (@options[:compress_assets] ? asset.compress : asset.filtered).strip
44
+ end.reject{ |x| x.length == 0 }.join(klass::JOIN_WITH)
45
+ next unless raw.length > 0
46
+ tag.content = raw
47
+ head << tag
48
+ else
49
+ collection.each do |asset|
50
+ data = asset.reference_with
51
+ tag = Nokogiri::XML::Node.new(data[:tag_name], document)
52
+ data[:attributes].each { |k, v| tag[k] = v unless v.nil? }
53
+ head << tag
54
+ end
55
+ end
56
+ end
57
+
58
+ #create font stylesheet
59
+ font_css = ''
60
+ fonts = {}
61
+ assets.of_type(Fuse::Document::Asset::Font).each do |asset|
62
+ (fonts[asset.face] ||= {})[asset.extension.to_sym] = asset
63
+ end
64
+ if fonts.length > 0
65
+ fonts.values.each do |formats|
66
+ first = formats.values.first
67
+ font_css << '@font-face{'
68
+ font_css << 'font-family: "%s";' % first.family
69
+ font_css << 'font-weight: %s;' % first.weight
70
+ font_css << 'font-style: %s;' % first.style
71
+ font_css << 'src: url("%s");' % formats[:eot].relative_path if formats[:eot]
72
+ css_formats = []
73
+ Fuse::Document::Asset::Font::CSS_FORMATS.each do |css_format|
74
+ css_formats << 'url("%s") format("%s")' % [
75
+ formats[css_format[:extension]].relative_path,
76
+ css_format[:format]
77
+ ] if formats[css_format[:extension]]
78
+ end
79
+ font_css << 'src: %s;' % css_formats.join(', ') if css_formats.any?
80
+ end
81
+ end
82
+ unless font_css.empty?
83
+ style_node = head.css('style:not([media]), style[media=all]').first || head.add_child(Nokogiri::XML::Node.new 'style', document)
84
+ style_node.content = font_css + style_node.content
85
+ end
86
+
87
+ #embed images and fonts
88
+ if @options[:embed_assets]
89
+ %w|@src @href @style style|.each do |xpath|
90
+ document.xpath('//' + xpath).each do |node|
91
+ assets.of_type(Fuse::Document::Asset::Image, Fuse::Document::Asset::Font).each do |asset|
92
+ node.content = node.content.gsub asset.relative_path, asset.to_datauri
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ document
99
+ end
100
+
101
+ def root
102
+ @root ||= File.directory?((opt = @options[:source])) ? opt : File.dirname(opt)
103
+ end
104
+
105
+ private
106
+
107
+ def source_xml?
108
+ source_path.match(/\.xml$/i)
109
+ end
110
+
111
+ def potential_xsl
112
+ @potential_xsl ||= potentials(:xsl, %w(xsl))
113
+ end
114
+
115
+ def potential_sources
116
+ @potential_docs ||= begin
117
+ extensions = %w|html htm|
118
+ extensions << 'xml' if potential_xsl.any?
119
+ potentials(:source, extensions)
120
+ end
121
+ end
122
+
123
+ def potentials(option, extensions)
124
+ if (option_value = @options[option])
125
+ raise Fuse::Exception::SourceUnknown::NotFound.new(option_value) unless File.exists?(option_value)
126
+ return [option_value] unless File.directory? option_value
127
+ end
128
+ Dir[File.join(option_value || root, "**/*.{#{extensions.join(',')}}")]
129
+ end
130
+
131
+ def expect_one(list, option, missing_exception)
132
+ raise missing_exception if list.empty?
133
+ raise Fuse::Exception::SourceUnknown::TooManySources.new(option, list) if list.length > 1
134
+ list.first
135
+ end
136
+
137
+ def assets
138
+ @assets ||= Asset[root]
139
+ end
140
+ end
@@ -0,0 +1,72 @@
1
+ require 'base64'
2
+
3
+ class Fuse::Document::Asset
4
+
5
+ def self.[](dir)
6
+ assets = Fuse::Document::AssetCollection.new
7
+ Dir[File.join dir, '**/*.*'].each do |full_path|
8
+ asset = self.for(full_path[dir.length..-1], dir)
9
+ assets << asset if asset
10
+ end
11
+ assets
12
+ end
13
+
14
+ def self.for(path, root = Dir.pwd)
15
+ full_path = File.join root, path
16
+ return unless File.exists? full_path
17
+ type = TYPES[(File.extname(path)[1..-1] || '').to_sym]
18
+ (@cache ||= {})[File.expand_path full_path] ||= type.new(path, root) if type
19
+ end
20
+
21
+ attr_reader :path
22
+
23
+ def initialize(path, root)
24
+ @root = root
25
+ @path = path
26
+ end
27
+
28
+ def full_path
29
+ @full_path ||= File.join @root, @path
30
+ end
31
+
32
+ def relative_path
33
+ @relative_path ||= path.sub(%r`^/`, '')
34
+ end
35
+
36
+ def raw
37
+ @raw ||= File.open(full_path, 'rb') { |f| f.read }
38
+ end
39
+
40
+ def filtered
41
+ @filtered ||= filter? ? filter : raw
42
+ end
43
+
44
+ def filter?
45
+ respond_to? :filter
46
+ end
47
+
48
+ def call(env)
49
+ if filter?
50
+ body = filter
51
+ [200, {'Content-Type' => type, 'Content-Length' => body.length.to_s}, [body]]
52
+ else
53
+ Rack::File.new(@root).call(env)
54
+ end
55
+ end
56
+
57
+ def type
58
+ @type ||= Rack::Mime.mime_type('.' + extension)
59
+ end
60
+
61
+ def to_datauri(compress = false)
62
+ 'data:%s;base64,%s' % [
63
+ type,
64
+ Base64.strict_encode64(compress && respond_to?(:compress) ? self.compress : raw)
65
+ ]
66
+ end
67
+
68
+ def extension
69
+ @extension ||= File.extname(path).downcase[1..-1]
70
+ end
71
+
72
+ end
@@ -0,0 +1,33 @@
1
+ class Fuse::Document::AssetCollection < Array
2
+
3
+ def of_type(*types)
4
+ self.class.new select { |a|
5
+ ret = types.include? a.class
6
+ types.each { |type| ret ||= a.is_a? type } unless ret
7
+ ret
8
+ }
9
+ end
10
+
11
+ def sort!
12
+ unsorted = Array.new(self)
13
+ clear
14
+ unsorted.each { |a| push_with_dependents a }
15
+ self
16
+ end
17
+
18
+ def sort
19
+ self.class.new(self).sort!
20
+ end
21
+
22
+ def <<(*args)
23
+ args.each { |arg| super(arg) unless include? arg }
24
+ self
25
+ end
26
+
27
+ def push_with_dependents(asset)
28
+ #todo check for circular references
29
+ asset.dependents.each { |d| push_with_dependents d }
30
+ self << asset
31
+ end
32
+
33
+ end
@@ -0,0 +1,145 @@
1
+ require 'sass'
2
+ require 'coffee-script'
3
+ require 'uglifier'
4
+
5
+ class Fuse::Document::Asset
6
+
7
+ module HasDependents
8
+ COMMENT_PATTERN = %r`^\s*(/\*[\s\S]*?\*/|(\s*//.*\s+)+)`
9
+ REQUIRE_PATTERN = %r`^\s*(?:\*|//)=\s+(require|require_glob)\s+(.+?)\s*$`
10
+
11
+ def dependents
12
+ ret = Fuse::Document::AssetCollection.new
13
+ local_root = File.dirname(full_path)
14
+ if (comments = raw[COMMENT_PATTERN])
15
+ comments.lines.each do |line|
16
+ if (match = REQUIRE_PATTERN.match(line))
17
+ case match[1]
18
+ when 'require'
19
+ [File.join(local_root, match[2])]
20
+ when 'require_glob'
21
+ Dir[File.join(local_root, match[2])]
22
+ else
23
+ []
24
+ end.map do |p|
25
+ Fuse::Document::Asset.for(p[@root.length..-1], @root)
26
+ end.reject do |p|
27
+ p.nil?
28
+ end.each do |p|
29
+ ret << p
30
+ end
31
+ end
32
+ end
33
+ end
34
+ ret
35
+ end
36
+ end
37
+
38
+ class StyleSheet < self
39
+ EMBED_WITH = 'style'
40
+ JOIN_WITH = ''
41
+ MEDIA_PATTERN = /\(([a-z]+(?:,\s*[a-z]+)*)\)\.[a-z]+$/i
42
+ include HasDependents
43
+ def reference_with
44
+ {
45
+ tag_name: 'link',
46
+ attributes: {
47
+ rel: 'stylesheet',
48
+ href: relative_path,
49
+ media: media
50
+ }
51
+ }
52
+ end
53
+ def media
54
+ @media ||= (match = MEDIA_PATTERN.match(path)) && match[1].split(/,\s*/).sort.join(', ')
55
+ end
56
+ def compress; ::Sass.compile raw, style: :compressed end
57
+ def type; 'text/css' end
58
+ class Sass < self
59
+ def filter; ::Sass.compile raw, style: :expanded end
60
+ end
61
+ end
62
+
63
+ class JavaScript < self
64
+ EMBED_WITH = 'script'
65
+ JOIN_WITH = ';'
66
+ include HasDependents
67
+ def reference_with
68
+ {
69
+ tag_name: 'script',
70
+ attributes: {
71
+ type: type,
72
+ src: relative_path
73
+ }
74
+ }
75
+ end
76
+ def compress; Uglifier.compile filtered end
77
+ def type; 'text/javascript' end
78
+ class Coffee < self
79
+ def filter; CoffeeScript.compile raw end
80
+ end
81
+ end
82
+
83
+ class Image < self
84
+ end
85
+
86
+ class Font < self
87
+ CSS_FORMATS = [
88
+ { extension: :woff, format: 'woff' },
89
+ { extension: :ttf, format: 'truetype' },
90
+ { extension: :otf, format: 'opentype' }
91
+ ]
92
+ MIME_TYPES = {
93
+ otf: 'application/x-font-opentype',
94
+ ttf: 'application/x-font-truetype',
95
+ }
96
+ VARIANT_PATTERN = %r`([^/]+?)(?:[-_ ](normal|bold|bolder|lighter|[1-9]00))?(?:[-_ ](normal|italic|oblique))?\.[a-z]+$`
97
+ def family; @family ||= variant[:family] end
98
+ def weight; @weight ||= variant[:weight] end
99
+ def style; @style ||= variant[:style] end
100
+ def variant
101
+ @variant ||= begin
102
+ match = VARIANT_PATTERN.match(path)
103
+ {
104
+ family: match[1],
105
+ weight: match[2] || 'normal',
106
+ style: match[3] || 'normal'
107
+ }
108
+ end
109
+ end
110
+ def face; @face ||= [family, weight, style].join('-') end
111
+ def type
112
+ MIME_TYPES[extension.to_sym] || super
113
+ end
114
+ end
115
+
116
+ class Xml < self
117
+ end
118
+
119
+ class Xsl < self
120
+ end
121
+
122
+ class Html < self
123
+ end
124
+
125
+ TYPES = {
126
+ css: StyleSheet,
127
+ scss: StyleSheet::Sass,
128
+ sass: StyleSheet::Sass,
129
+ js: JavaScript,
130
+ coffee: JavaScript::Coffee,
131
+ jpg: Image,
132
+ jpeg: Image,
133
+ png: Image,
134
+ gif: Image,
135
+ svg: Image,
136
+ ttf: Font,
137
+ woff: Font,
138
+ eot: Font,
139
+ otf: Font,
140
+ xml: Xml,
141
+ xsl: Xsl,
142
+ htm: Html,
143
+ html: Html
144
+ }
145
+ end
@@ -0,0 +1,25 @@
1
+ class Fuse::Exception < ::RuntimeError
2
+ class SourceUnknown < self
3
+ class TooManySources < self
4
+ attr_reader :option_name, :options
5
+ def initialize(option_name, options)
6
+ @option_name = option_name
7
+ @options = options
8
+ end
9
+ end
10
+ class NotFound < self
11
+ attr_reader :path
12
+ def initialize(path)
13
+ @path = path
14
+ end
15
+ def message; "Couldn't find '#{path}'." end
16
+ end
17
+ def message; 'Couldn\'t determine source document. Please specify one with --source.' end
18
+ end
19
+ class XslMissing
20
+ def message
21
+ 'Couldn\'t locate an XSL stylesheet to transform the source document. Please specify one with --xsl.'
22
+ end
23
+ end
24
+ def message; end
25
+ end
data/lib/fuse/main.rb ADDED
@@ -0,0 +1,129 @@
1
+ require 'optparse'
2
+ require 'thin'
3
+
4
+ module Fuse
5
+
6
+ DEFAULTS = {
7
+ common: {
8
+ source: '.',
9
+ encoding: 'UTF-8'
10
+ },
11
+ server: {
12
+ port: 9460,
13
+ embed_assets: false
14
+ },
15
+ compile: {
16
+ embed_assets: true,
17
+ compress_assets: true
18
+ }
19
+ }
20
+
21
+ def self.main
22
+
23
+ options = {}
24
+
25
+ options_parser = OptionParser.new do |opts|
26
+
27
+ opts.banner = 'Usage: fuse [command] [options]'
28
+ opts.separator ''
29
+ opts.separator 'Commands:'
30
+ opts.separator ' server : Run a local testing server'
31
+ opts.separator ' compile : Compile the document and send to STDOUT'
32
+ opts.separator ''
33
+ opts.separator 'Options:'
34
+
35
+ opts.on('-c',
36
+ '--[no-]compress-assets',
37
+ 'Compress assets.'
38
+ ) do |compress|
39
+ options[:compress_assets] = compress
40
+ end
41
+
42
+ opts.on('-e',
43
+ '--encoding CHARSET',
44
+ "Output encoding. Default is #{DEFAULTS[:common][:encoding]}."
45
+ ) do |e|
46
+ options[:encoding] = e
47
+ end
48
+
49
+ opts.on('-m',
50
+ '--[no-]embed-assets',
51
+ 'Embed assets.'
52
+ ) do |embed|
53
+ options[:embed_assets] = embed
54
+ end
55
+
56
+ opts.on('-p',
57
+ '--port PORT',
58
+ Integer,
59
+ "Port on which to listen (only with 'server' command). Default is #{DEFAULTS[:server][:port]}."
60
+ ) do |port|
61
+ options[:port] = port
62
+ end
63
+
64
+ opts.on('-s',
65
+ '--source [FILE|DIR]',
66
+ 'The source directory, or HTML or XML document. Default is current directory.'
67
+ ) do |doc|
68
+ options[:source] = doc
69
+ end
70
+
71
+ opts.on('-t',
72
+ '--title TITLE',
73
+ 'HTML document title.'
74
+ ) do |t|
75
+ options[:title] = t
76
+ end
77
+
78
+ opts.on('-x',
79
+ '--xsl FILE',
80
+ 'XSL transformation stylesheet. Default is current directory.'
81
+ ) do |xsl|
82
+ abort "#{xsl} isn't a valid XSL stylesheet" unless xsl.match(/\.xsl$/i)
83
+ options[:xsl] = xsl
84
+ end
85
+
86
+ opts.on_tail('-h', '--help', 'Show this message.') { puts opts.to_s }
87
+
88
+ end
89
+
90
+ options_parser.parse!
91
+
92
+ options = merge_defaults(options)
93
+
94
+ case ARGV[0]
95
+
96
+ when 'server'
97
+ Thin::Server.start('0.0.0.0', options[:port]) do
98
+ use Rack::ShowExceptions
99
+ run Server.new(options)
100
+ end
101
+
102
+ when 'compile'
103
+ begin
104
+ print Document.new(options).to_s
105
+ rescue Exception::SourceUnknown::TooManySources
106
+ $stderr.puts "Found more than one potential #{$!.option_name} document. Please specify one with --#{$!.option_name}."
107
+ $stderr.puts $!.options.join "\n"
108
+ rescue Exception
109
+ $!.message ? $stderr.puts($!.message) : raise
110
+ end
111
+
112
+ else
113
+ puts options_parser
114
+
115
+ end
116
+
117
+ end
118
+
119
+ private
120
+
121
+ def self.merge_defaults(options)
122
+ if (defaults = DEFAULTS[(ARGV[0] || '').to_sym])
123
+ defaults.merge(DEFAULTS[:common]).merge(options)
124
+ else
125
+ options
126
+ end
127
+ end
128
+
129
+ end
@@ -0,0 +1,71 @@
1
+ require 'nokogiri'
2
+
3
+ class Fuse::Server
4
+
5
+ def initialize(options)
6
+ @options = options
7
+ end
8
+
9
+ def call(env)
10
+
11
+ @env = env
12
+
13
+ call_options = @options.merge Hash[request.GET.map{ |k, v| [k.to_sym, v] }]
14
+
15
+ if (asset = Fuse::Document::Asset.for(request.path, File.directory?(@options[:source]) ? @options[:source] : File.dirname(@options[:source])))
16
+ return asset.call(env)
17
+ end
18
+
19
+ begin
20
+ doc = Fuse::Document.new(call_options)
21
+ rescue Fuse::Exception::SourceUnknown::TooManySources
22
+ doc = render_list($!.options, $!.option_name)
23
+ rescue Fuse::Exception
24
+ if $!.message
25
+ doc = render_error($!.message)
26
+ else
27
+ raise
28
+ end
29
+ end
30
+
31
+ [200, {'Content-Type' => 'text/html'}, [doc.to_s]]
32
+
33
+ end
34
+
35
+ private
36
+
37
+ def request
38
+ Rack::Request.new(@env)
39
+ end
40
+
41
+ def render_error(text)
42
+ render_body do |h|
43
+ h.p { h.text text }
44
+ end
45
+ end
46
+
47
+ def render_list(assets, key)
48
+ render_body do |h|
49
+ h.h3 { h.text "Choose #{key}:" }
50
+ h.ul {
51
+ assets.each do |asset|
52
+ h.li {
53
+ h.a(href: '?' + Rack::Utils.build_query(request.GET.merge(key.to_s => asset))) {
54
+ h.text asset
55
+ }
56
+ }
57
+ end
58
+ }
59
+ end
60
+ end
61
+
62
+ def render_body
63
+ Nokogiri::HTML::Builder.new do |h|
64
+ h.html {
65
+ h.head { h.title { h.text 'Fuse' } }
66
+ h.body { yield h }
67
+ }
68
+ end.to_html
69
+ end
70
+
71
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fuse
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Neil E. Pearson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-03-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thin
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: uglifier
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 1.5.6
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.5.6
55
+ - !ruby/object:Gem::Dependency
56
+ name: coffee-script
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '2.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sass
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: 3.2.5
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 3.2.5
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 2.12.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: 2.12.2
97
+ description: ! 'Portable document authoring. Fuse HTML, JavaScript, CSS, images and
98
+ fonts into standalone HTML files.
99
+
100
+ '
101
+ email: neil@helium.net.au
102
+ executables:
103
+ - fuse
104
+ extensions: []
105
+ extra_rdoc_files: []
106
+ files:
107
+ - bin/fuse
108
+ - lib/fuse/document/asset.rb
109
+ - lib/fuse/document/asset_collection.rb
110
+ - lib/fuse/document/asset_types.rb
111
+ - lib/fuse/document.rb
112
+ - lib/fuse/exceptions.rb
113
+ - lib/fuse/main.rb
114
+ - lib/fuse/server.rb
115
+ - lib/fuse.rb
116
+ - README.md
117
+ - LICENSE
118
+ homepage: https://github.com/hx/fuse
119
+ licenses:
120
+ - Apache 2.0
121
+ metadata: {}
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: 1.9.0
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 2.0.0
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: Fuse HTML, CSS, JavaScript, Fonts and Images into a single file.
142
+ test_files: []