hldr 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.
- data/Rakefile +8 -0
- data/Readme.md +78 -0
- data/bin/hldr +2 -0
- data/lib/hldr.rb +2 -0
- data/lib/hldr/hldr_entry.rb +53 -0
- data/lib/hldr/hldr_globals.rb +1 -0
- data/lib/hldr/hldr_processor.rb +86 -0
- data/lib/hldr/inliners/image_inliner.rb +19 -0
- data/lib/hldr/inliners/inliner.rb +48 -0
- data/lib/hldr/inliners/script_inliner.rb +25 -0
- data/lib/hldr/inliners/style_inliner.rb +29 -0
- data/test/test_inliner.rb +8 -0
- metadata +76 -0
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# Hldr
|
2
|
+
A Ruby gem to generate portable interactive documents by compiling all linked assets from an HTML document into a single file (rendered to `stdout`). The value of the Word document is diminishing by the minute!
|
3
|
+
|
4
|
+
Suppose you have a somewhat complicated, well broken out HTML document or front-end prototype that you want to quickly show a non-technical colleague, Bernard. Your document may look like this:
|
5
|
+
|
6
|
+
.
|
7
|
+
└── img
|
8
|
+
│ ├── icons
|
9
|
+
│ │ └── star.png
|
10
|
+
│ └── cake_decors.png
|
11
|
+
│ └── kittens.png
|
12
|
+
├── js
|
13
|
+
│ ├── app.js
|
14
|
+
│ └── jquery.js
|
15
|
+
├── css
|
16
|
+
│ ├── app.css
|
17
|
+
│ └── bootstrap.css
|
18
|
+
│
|
19
|
+
└── cakeRecipe.html
|
20
|
+
|
21
|
+
Wouldn't it be great if we could quickly compile all of this into a **single** file to give Bernard?
|
22
|
+
|
23
|
+
$ hldr cakeRecipe.html > flatfile.html
|
24
|
+
|
25
|
+

|
26
|
+
|
27
|
+
Yeah, obviously the output is a lot bigger than the original structure or a zip archive (especially if embedding images), but this is easier and cleaner for all parties involved. The vision for Hldr is twofold:
|
28
|
+
|
29
|
+
1. Allowing (even non-technical) content creators to more easily leverage powerful interactive frameworks within their document.
|
30
|
+
1. Make HTML front-end prototype sharing quick and easy.
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
Installation is simple, just suck down the gem via `gem install hldr`
|
35
|
+
|
36
|
+
## Really advanced usage
|
37
|
+
|
38
|
+
$ hldr new cakeRecipe
|
39
|
+
$ cd cakeRecipe
|
40
|
+
|
41
|
+
$ hldr add bootstrap-3.0.0-rc2
|
42
|
+
$ hldr add jquery-mobile js
|
43
|
+
$ hldr add d3 js
|
44
|
+
$ hldr add flatui css
|
45
|
+
$ hldr add anotherCssJsFw
|
46
|
+
|
47
|
+
As a bonus, swapping out assets is a breeze:
|
48
|
+
|
49
|
+
$ hldr rm bootstrap-3.0.0-rc2
|
50
|
+
$ hldr add bootstrap-2.3.0
|
51
|
+
$ hldr cakeRecipe.md > flatCakeRecipeFile.html
|
52
|
+
|
53
|
+
## Example Config File
|
54
|
+
|
55
|
+
scaffolding:
|
56
|
+
- http://somecdn.com/css/bootstrap.min.css
|
57
|
+
- http://somecdn.com/js/BBD-G23-4SIOU23-452 : js
|
58
|
+
|
59
|
+
## The Future
|
60
|
+
- [x] ship anything
|
61
|
+
- [ ] compress images, html, js and css
|
62
|
+
- [ ] templates
|
63
|
+
- [ ] support md
|
64
|
+
- [ ] support haml
|
65
|
+
- [ ] Keep everything in .hldr cache
|
66
|
+
- [ ] fetch remotes
|
67
|
+
- [ ] support inline of css `@import`
|
68
|
+
- [ ] support inline of css `@import` media queries
|
69
|
+
- [ ] support inline of css fonts
|
70
|
+
- [ ] option to compress images below 32k for IE8 support
|
71
|
+
- [ ] support css image inline
|
72
|
+
- [ ] create gem that installs to path
|
73
|
+
- [ ] handle css, scss, less, sass
|
74
|
+
- [ ] handle requireJs
|
75
|
+
- [ ] set max cache size in config
|
76
|
+
|
77
|
+
## Bugs
|
78
|
+
* Handle malformed input (ie file not found, etc)
|
data/bin/hldr
ADDED
data/lib/hldr.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'trollop'
|
2
|
+
require 'hldr/hldr_processor'
|
3
|
+
require 'hldr/hldr_globals'
|
4
|
+
|
5
|
+
hldr_opts = Trollop::options do
|
6
|
+
version "Hldr #{HLDR_VERSION} (c) 2013 Nate Fisher. \n\nThere can be only one!"
|
7
|
+
banner <<-EOS
|
8
|
+
Hldr - Generate a flat HTML file from linked assets.
|
9
|
+
|
10
|
+
Usage:
|
11
|
+
hldr [options] <file>
|
12
|
+
hldr <command> [args]
|
13
|
+
|
14
|
+
Examples:
|
15
|
+
hldr index.html > flat.html
|
16
|
+
hldr index.md > flat.html
|
17
|
+
|
18
|
+
Commands:
|
19
|
+
update Updates external content in the config file and stores to cache
|
20
|
+
init Creates a config file
|
21
|
+
new NAME Creates a new document folder, empty contents and a config file
|
22
|
+
|
23
|
+
Options:
|
24
|
+
|
25
|
+
EOS
|
26
|
+
|
27
|
+
opt :no_embed, "Inhibits base64 data URI image embedding", :default => true
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
hldr_cmd = ARGV.shift
|
32
|
+
case hldr_cmd
|
33
|
+
when "update"
|
34
|
+
# update cache from config file
|
35
|
+
when "init"
|
36
|
+
# create config file
|
37
|
+
HldrProcessor::generate_config
|
38
|
+
HldrProcessor::generate_cache
|
39
|
+
when "new"
|
40
|
+
if ARGV.first.nil?
|
41
|
+
puts "Name of document required for NEW command."
|
42
|
+
else
|
43
|
+
# create project folder
|
44
|
+
end
|
45
|
+
else
|
46
|
+
# no subcommand entered
|
47
|
+
if hldr_cmd.nil?
|
48
|
+
Trollop::die "No input given."
|
49
|
+
else
|
50
|
+
# Processing file
|
51
|
+
puts HldrProcessor.new(hldr_cmd, hldr_opts).to_s
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
HLDR_VERSION = "0.1.1"
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'yaml'
|
3
|
+
require 'hldr/inliners/script_inliner'
|
4
|
+
require 'hldr/inliners/image_inliner'
|
5
|
+
require 'hldr/inliners/style_inliner'
|
6
|
+
|
7
|
+
class HldrProcessor
|
8
|
+
|
9
|
+
def initialize(location, options)
|
10
|
+
begin
|
11
|
+
@doc = Nokogiri::HTML(open(location))
|
12
|
+
rescue
|
13
|
+
puts "Could not open input file '#{location}'"
|
14
|
+
exit(1)
|
15
|
+
end
|
16
|
+
@options = options
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
|
21
|
+
## add config file scaffolding
|
22
|
+
add_scaffolding
|
23
|
+
|
24
|
+
## process inlining
|
25
|
+
inline_assets
|
26
|
+
|
27
|
+
return @doc.to_html
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_scaffolding
|
32
|
+
|
33
|
+
hldr_config = YAML.load_file('.hldrconfig')
|
34
|
+
hldr_config["scaffolding"].each do |resource|
|
35
|
+
|
36
|
+
# if ends in .css or is a hash with value of css, add style
|
37
|
+
if resource[-4..-1] == ".css"
|
38
|
+
|
39
|
+
css_res = Nokogiri::XML::Node.new "style", @doc
|
40
|
+
css_res.content = open(resource).read
|
41
|
+
css_res[:type] = "text/css"
|
42
|
+
|
43
|
+
head = @doc.at_css("html")
|
44
|
+
head << css_res
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
def inline_assets
|
53
|
+
|
54
|
+
inliners = {
|
55
|
+
:link => StyleInliner,
|
56
|
+
:script => ScriptInliner
|
57
|
+
}
|
58
|
+
|
59
|
+
# inhibit embedding images if flag set
|
60
|
+
if @options[:no_embed]
|
61
|
+
inliners[:img] = ImageInliner
|
62
|
+
end
|
63
|
+
|
64
|
+
inliners.each do |tag, inliner|
|
65
|
+
|
66
|
+
@doc.css(tag.to_s).each do |element|
|
67
|
+
inliner::embed(element)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
# creates a cache location
|
77
|
+
def HldrProcessor.generate_cache
|
78
|
+
Dir::mkdir ".hldr" if !Dir::exist? ".hldr"
|
79
|
+
end
|
80
|
+
|
81
|
+
# creates a new config file
|
82
|
+
def HldrProcessor.generate_config
|
83
|
+
File::open ".hldrconfig", "w" if !File::exist? ".hldrconfig"
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'hldr/inliners/inliner'
|
3
|
+
|
4
|
+
class ImageInliner < Inliner
|
5
|
+
def ImageInliner.embed(element)
|
6
|
+
|
7
|
+
# try to retrieve content
|
8
|
+
content = getContent(element[:src])
|
9
|
+
return if content[:data].nil?
|
10
|
+
|
11
|
+
# create embedded data uri
|
12
|
+
data = "data:#{content[:type]};base64,"
|
13
|
+
data += Base64::encode64(content[:data])
|
14
|
+
|
15
|
+
# embed content
|
16
|
+
element[:src] = data
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
class Inliner
|
4
|
+
|
5
|
+
def Inliner.get_image_extension(local_file_path)
|
6
|
+
# take from Alain Beauvois's post in
|
7
|
+
# http://stackoverflow.com/questions/4600679/detect-mime-type-of-uploaded-file-in-ruby
|
8
|
+
|
9
|
+
png = Regexp.new("\x89PNG".force_encoding("binary"))
|
10
|
+
jpg = Regexp.new("\xff\xd8\xff\xe0\x00\x10JFIF".force_encoding("binary"))
|
11
|
+
jpg2 = Regexp.new("\xff\xd8\xff\xe1(.*){2}Exif".force_encoding("binary"))
|
12
|
+
|
13
|
+
case IO.read(local_file_path, 10)
|
14
|
+
when /^GIF8/
|
15
|
+
'image/gif'
|
16
|
+
when /^#{png}/
|
17
|
+
'image/png'
|
18
|
+
when /^#{jpg}/
|
19
|
+
'image/jpg'
|
20
|
+
when /^#{jpg2}/
|
21
|
+
'image/jpg'
|
22
|
+
else
|
23
|
+
mime_type = `file #{local_file_path} --mime-type`.gsub("\n", '') # Works on linux and mac
|
24
|
+
raise UnprocessableEntity, "unknown file type" if !mime_type
|
25
|
+
mime_type.split(':')[1].split('/')[1].gsub('x-', '').gsub(/jpeg/, 'jpg').gsub(/text/, 'txt').gsub(/x-/, '')
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def Inliner.getContent(location)
|
31
|
+
|
32
|
+
content = Hash.new
|
33
|
+
|
34
|
+
begin
|
35
|
+
if location[0..3] == "http"
|
36
|
+
content[:data] = open(location).read
|
37
|
+
content[:type] = open(location).meta["content-type"]
|
38
|
+
else
|
39
|
+
content[:data] = File::open(location, "rb").read
|
40
|
+
content[:type] = self.get_image_extension(location)
|
41
|
+
end
|
42
|
+
rescue
|
43
|
+
end
|
44
|
+
|
45
|
+
return content
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'hldr/inliners/inliner'
|
2
|
+
|
3
|
+
class ScriptInliner < Inliner
|
4
|
+
def ScriptInliner.embed(element)
|
5
|
+
|
6
|
+
# ignore if content exists within tags
|
7
|
+
return if element.content &&
|
8
|
+
!element.content.strip.empty?
|
9
|
+
|
10
|
+
# try to retrieve content
|
11
|
+
content = getContent(element[:src])
|
12
|
+
return if content[:data].nil?
|
13
|
+
|
14
|
+
# remove src attribute
|
15
|
+
element.attributes["src"].remove
|
16
|
+
|
17
|
+
# force script type to javascript if null
|
18
|
+
element[:type] = "text/javascript" if
|
19
|
+
element[:type].nil? || element[:type] == ""
|
20
|
+
|
21
|
+
# embed content
|
22
|
+
element.content = content[:data]
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'hldr/inliners/inliner'
|
2
|
+
|
3
|
+
class StyleInliner < Inliner
|
4
|
+
def StyleInliner.embed(element)
|
5
|
+
|
6
|
+
# try to retrieve content
|
7
|
+
content = getContent(element[:href])
|
8
|
+
return if content[:data].nil?
|
9
|
+
|
10
|
+
# keep media and type; remove all other params
|
11
|
+
whitelist = ["type", "media"]
|
12
|
+
element.attributes.each do |key, val|
|
13
|
+
if !whitelist.include?(key)
|
14
|
+
element.attributes[key].remove
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# force rel type to style if null
|
19
|
+
element[:type] = "text/css" if
|
20
|
+
element[:type].nil? || element[:type] == ""
|
21
|
+
|
22
|
+
# rename tag to style
|
23
|
+
element.name = "style"
|
24
|
+
|
25
|
+
# embed content
|
26
|
+
element.content = content[:data]
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hldr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nate Fisher
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-10-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nokogiri
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.6'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.6'
|
30
|
+
description: ! 'Hldr is a utility to compile all linked assets from an html document
|
31
|
+
into a single file, rendered to STDOUT. The point is to allow content creators to
|
32
|
+
more easily use powerful interactive frameworks within their document. '
|
33
|
+
email: nate.scott.fisher@gmail.com
|
34
|
+
executables:
|
35
|
+
- hldr
|
36
|
+
extensions: []
|
37
|
+
extra_rdoc_files: []
|
38
|
+
files:
|
39
|
+
- bin/hldr
|
40
|
+
- lib/hldr.rb
|
41
|
+
- lib/hldr/inliners/image_inliner.rb
|
42
|
+
- lib/hldr/inliners/inliner.rb
|
43
|
+
- lib/hldr/inliners/style_inliner.rb
|
44
|
+
- lib/hldr/inliners/script_inliner.rb
|
45
|
+
- lib/hldr/hldr_processor.rb
|
46
|
+
- lib/hldr/hldr_entry.rb
|
47
|
+
- lib/hldr/hldr_globals.rb
|
48
|
+
- test/test_inliner.rb
|
49
|
+
- Readme.md
|
50
|
+
- Rakefile
|
51
|
+
homepage: http://github.com/thenatefisher/hldr
|
52
|
+
licenses:
|
53
|
+
- MIT
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
requirements: []
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.8.24
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: Generate a flat HTML file from linked assets
|
76
|
+
test_files: []
|