fuse 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []