dothtml 0.0.3
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 +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +52 -0
- data/Rakefile +9 -0
- data/bin/dothtml +3 -0
- data/dothtml.gemspec +30 -0
- data/lib/dothtml/dot_helper.rb +126 -0
- data/lib/dothtml/dot_task.rb +78 -0
- data/lib/dothtml/version.rb +3 -0
- data/lib/dothtml.rb +5 -0
- data/templates/Gemfile +5 -0
- data/templates/Guardfile +6 -0
- data/templates/Rakefile +23 -0
- data/templates/behavior.js +86 -0
- data/templates/d3.v3.js +9233 -0
- data/templates/index.html.erb +34 -0
- data/templates/style.css +113 -0
- data/test/minitest_helper.rb +6 -0
- data/test/test_dot_helper.rb +54 -0
- data/test/test_dot_task.rb +4 -0
- data/test/test_dothtml.rb +7 -0
- metadata +172 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1e761c595a94b3bbafdbb7b7796ecc2b1f8a6690
|
4
|
+
data.tar.gz: 2aa1330586afdd6bd045b35c29255ee70d0d3ddc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 27d82f94a44ca6052014e949f6bd3dfaf3d3a2958c03ff71016a6ea9418b7f6ec27d84ccc5f2d888036651a461f2e73fe17e6a3380383b0599a2b2487e604df1
|
7
|
+
data.tar.gz: dee8c3c7154326da5120f175e6e4be26a3c1821ae35f922aff673bb1ee6614bd1764a9a455c40b61c25526c606fc402a5f58df8a1c49fc474f7e0dcf807abef3
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
## 0.0.3 - 2014-12-23
|
4
|
+
### Added
|
5
|
+
- Rule to generate an svg file
|
6
|
+
|
7
|
+
## 0.0.2 - 2014-12-10
|
8
|
+
### Added
|
9
|
+
- This CHANGELOG
|
10
|
+
- Added behavior and style files. (no longer in template file)
|
11
|
+
|
12
|
+
### Removed
|
13
|
+
- Removed temporary svg file and associated Rakefile rule.
|
14
|
+
- Removed documentation and radioboxes when not necessary.
|
15
|
+
|
16
|
+
## 0.0.1 - 2014-12-08
|
17
|
+
### Added
|
18
|
+
- Rakefile task rule to convert dot files to svg files
|
19
|
+
- Rakefile rule to convert svg files to html files
|
20
|
+
- Guardfile to auto generate site
|
21
|
+
|
22
|
+
### Remove
|
23
|
+
- Removed embedding of svg images. Not ready for prime time yet.
|
24
|
+
- Removed requirement on liquid gem. Default template now in erb.
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Keenan Brock
|
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,52 @@
|
|
1
|
+
# Dothtml
|
2
|
+
|
3
|
+
Graphviz is a great visualization tool for coding digraphs.
|
4
|
+
|
5
|
+
d3 is a great tool for dynamic html presentations.
|
6
|
+
|
7
|
+
This tool:
|
8
|
+
|
9
|
+
- converts graphviz dot files into html.
|
10
|
+
- provides sample guard file
|
11
|
+
- Rake tasks
|
12
|
+
- hacks class attributes to embed into html file
|
13
|
+
- embeds style into html file
|
14
|
+
|
15
|
+
TODO:
|
16
|
+
- add radio button for class association
|
17
|
+
- embed external svg images into html file
|
18
|
+
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'dothtml'
|
26
|
+
```
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install dothtml
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
TODO: Write usage instructions here
|
39
|
+
|
40
|
+
## Similar Projects
|
41
|
+
|
42
|
+
- https://github.com/ioquatix/graphviz
|
43
|
+
- https://github.com/kui/octopress-graphviz
|
44
|
+
- https://github.com/glejeune/Ruby-Graphviz
|
45
|
+
|
46
|
+
## Contributing
|
47
|
+
|
48
|
+
1. Fork it ( https://github.com/[my-github-username]/dothtml/fork )
|
49
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
50
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
51
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
52
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/dothtml
ADDED
data/dothtml.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dothtml/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "dothtml"
|
8
|
+
spec.version = Dothtml::VERSION
|
9
|
+
spec.authors = ["Keenan Brock"]
|
10
|
+
spec.email = ["keenan@thebrocks.net"]
|
11
|
+
spec.summary = %q{Make conversion of dot to html easier}
|
12
|
+
spec.description = %q{Make conversion of dot to html easier}
|
13
|
+
spec.homepage = "http://github.com/kbrock/dothtml"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'guard'
|
22
|
+
spec.add_dependency 'guard-rake'
|
23
|
+
spec.add_dependency 'nokogiri'
|
24
|
+
# possibly remove this
|
25
|
+
spec.add_dependency 'tilt'
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
29
|
+
spec.add_development_dependency "minitest"
|
30
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require "tilt"
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'set'
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
class DotHelper
|
7
|
+
def initialize(svg_contents)
|
8
|
+
@svg_contents = svg_contents
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse_dom(contents)
|
12
|
+
Nokogiri::XML.parse(contents)
|
13
|
+
end
|
14
|
+
|
15
|
+
def dom
|
16
|
+
@dom ||= parse_dom(@svg_contents)
|
17
|
+
end
|
18
|
+
|
19
|
+
def extractChoices
|
20
|
+
end
|
21
|
+
|
22
|
+
def descriptions?
|
23
|
+
#dom.css("")
|
24
|
+
end
|
25
|
+
|
26
|
+
def extractTitle
|
27
|
+
dom.css("title").first.content()
|
28
|
+
end
|
29
|
+
|
30
|
+
# this currently has too many limitations
|
31
|
+
# working on making this more friendly
|
32
|
+
# assume unique list of filenames
|
33
|
+
def images
|
34
|
+
embedded_images = Set.new
|
35
|
+
|
36
|
+
defs = dom.create_element("def")
|
37
|
+
|
38
|
+
# assuming the images are the correct size, declare their size
|
39
|
+
dom.css("image").each do |img|
|
40
|
+
file_name = img.attributes["href"].value
|
41
|
+
id = file_name.split(".").first.split("/").last
|
42
|
+
if file_name =~ /\.svg$/ && ! embedded_images.include?(file_name)
|
43
|
+
src = parse_dom(File.read(file_name)).at("svg")
|
44
|
+
g = dom.create_element("g", id: id,
|
45
|
+
width: src["width"], height: src["height"])
|
46
|
+
defs.add_child(g)
|
47
|
+
src.children.each do |child|
|
48
|
+
g.add_child(child.clone)
|
49
|
+
end
|
50
|
+
embedded_images << file_name
|
51
|
+
end
|
52
|
+
|
53
|
+
img.name="use"
|
54
|
+
img.attributes["href"].value="##{id}"
|
55
|
+
#img.attributes["width"].remove
|
56
|
+
#img.attributes["height"].remove
|
57
|
+
#img.attributes["preserveAspectRatio"].remove
|
58
|
+
end
|
59
|
+
defs
|
60
|
+
end
|
61
|
+
|
62
|
+
def extract_id_class(old_id)
|
63
|
+
if old_id =~ /(.*?) ?class=["']?(.*?)['"]?$/
|
64
|
+
[$1, $2]
|
65
|
+
else
|
66
|
+
[old_id]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def merge_id_class(old_id, old_class)
|
71
|
+
new_id, new_class = extract_id_class(old_id)
|
72
|
+
[new_id, [old_class, new_class].compact.join(" ")]
|
73
|
+
end
|
74
|
+
|
75
|
+
# some nodes are of the form <div id="x1 class='other'" class="c1">
|
76
|
+
# assume (class= is present)
|
77
|
+
def fix_node_id(node)
|
78
|
+
new_id, new_class = merge_id_class(node["id"], node["class"])
|
79
|
+
node["id"] = new_id
|
80
|
+
node["class"] = new_class
|
81
|
+
node
|
82
|
+
end
|
83
|
+
|
84
|
+
def embed_images
|
85
|
+
dom.at("svg").children.before(images)
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
def fix_ids
|
90
|
+
dom.xpath("//*[contains(@id,'class=')]").each { |n| fix_node_id(n) }
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
# uses a fragment to remove extra xml declarations
|
96
|
+
def to_xml
|
97
|
+
dom.at("svg").to_xml
|
98
|
+
end
|
99
|
+
|
100
|
+
def write(file_name, template_name, locals)
|
101
|
+
File.write(file_name, Tilt.new(template_name).render(binding, locals))
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.from_dotfile(filename)
|
105
|
+
new(svg_from_dot(File.read(filename)))
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.from_dot(contents)
|
109
|
+
new(svg_from_dot(contents))
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.svg_from_dot(contents)
|
113
|
+
Open3.popen3('dot -Tsvg') do |stdin, stdout, stderr|
|
114
|
+
stdout.binmode
|
115
|
+
stdin.print contents
|
116
|
+
stdin.close
|
117
|
+
|
118
|
+
err = stderr.read
|
119
|
+
if !err.nil? && !err.strip.empty?
|
120
|
+
raise "Error from graphviz:\n#{err}"
|
121
|
+
end
|
122
|
+
|
123
|
+
stdout.read.tap { |str| str.force_encoding 'UTF-8' }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
require_relative 'dot_helper'
|
4
|
+
|
5
|
+
module Dothtml
|
6
|
+
class DotTask < Rake::TaskLib
|
7
|
+
attr_accessor :template
|
8
|
+
attr_accessor :style
|
9
|
+
attr_accessor :behavior
|
10
|
+
attr_accessor :cdn
|
11
|
+
attr_accessor :d3js
|
12
|
+
|
13
|
+
attr_accessor :dot_folder
|
14
|
+
attr_accessor :html_folder
|
15
|
+
|
16
|
+
def initialize(name = :dot)
|
17
|
+
templates = File.expand_path(File.join(File.dirname(__FILE__), "..", "..","templates"))
|
18
|
+
@name = name
|
19
|
+
@style = File.join(templates, 'style.css')
|
20
|
+
@behavior = File.join(templates, 'behavior.js')
|
21
|
+
@template = File.join(templates, 'index.html.erb')
|
22
|
+
self.cdn = true
|
23
|
+
yield self if block_given?
|
24
|
+
define
|
25
|
+
end
|
26
|
+
|
27
|
+
def cdn=(val)
|
28
|
+
@d3js = val ? "//cdnjs.cloudflare.com/ajax/libs/d3/3.4.13/d3.min.js" : "d3.v3.js"
|
29
|
+
end
|
30
|
+
|
31
|
+
def define
|
32
|
+
desc "convert dot file into an html file"
|
33
|
+
task :dot_html, [:src, :target] do |t, params|
|
34
|
+
source = params[:src]
|
35
|
+
target = params[:target]
|
36
|
+
|
37
|
+
puts "#{source} -> #{target}"
|
38
|
+
|
39
|
+
doc = DotHelper.from_dotfile(source).fix_ids#.embed_images
|
40
|
+
doc.write target, @template,
|
41
|
+
title: doc.extractTitle,
|
42
|
+
body: doc.to_xml,
|
43
|
+
choices: doc.extractChoices,
|
44
|
+
descriptions: doc.descriptions?,
|
45
|
+
style: File.read(style),
|
46
|
+
behavior: File.read(behavior),
|
47
|
+
d3js: d3js
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "convert dot file into an svg file"
|
51
|
+
task :dot_svg, [:src, :target] do |t, params|
|
52
|
+
source = params[:src]
|
53
|
+
target = params[:target]
|
54
|
+
|
55
|
+
puts "#{source} -> #{target}"
|
56
|
+
|
57
|
+
doc = DotHelper.from_dotfile(source).fix_ids#.embed_images
|
58
|
+
File.write(target, doc.to_xml)
|
59
|
+
end
|
60
|
+
|
61
|
+
rule '.html' => [".dot", style, template, behavior] do |t|
|
62
|
+
Rake::Task["dot_html"].execute(:target => t.name, :src => t.source)
|
63
|
+
Rake::Task["refresh_browser"].invoke
|
64
|
+
end
|
65
|
+
|
66
|
+
rule '.svg' => [".dot"] do |t|
|
67
|
+
Rake::Task["dot_svg"].execute(:target => t.name, :src => t.source)
|
68
|
+
end
|
69
|
+
|
70
|
+
#TODO find proper tab, offer non chrome options
|
71
|
+
desc "use applescript to refresh front most window in chrome"
|
72
|
+
task :refresh_browser do
|
73
|
+
puts "refreshing browser"
|
74
|
+
`osascript -e 'tell application "Google Chrome" to tell the active tab of its first window to reload'`
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/dothtml.rb
ADDED
data/templates/Gemfile
ADDED
data/templates/Guardfile
ADDED
data/templates/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
# Rakefile to build dot files
|
3
|
+
# dependencies:
|
4
|
+
# mac
|
5
|
+
# graphviz to convert dot files to svg
|
6
|
+
|
7
|
+
require "dothtml/dot_task"
|
8
|
+
|
9
|
+
Dothtml::DotTask.new do |t|
|
10
|
+
#t.cdn = true
|
11
|
+
#t.template = 'index.html.liquid'
|
12
|
+
#t.style = 'style.css'
|
13
|
+
#t.behavior = 'behavior.js'
|
14
|
+
end
|
15
|
+
|
16
|
+
task :default => :html
|
17
|
+
|
18
|
+
#task :html => %w(file1.html file2.html)
|
19
|
+
task :html do
|
20
|
+
Dir.glob("*.dot").each do |f|
|
21
|
+
Rake::Task[f.sub(/\.dot$/, '.html')].invoke
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
var current_edge = {};
|
2
|
+
|
3
|
+
function markClass(id, className, enabled) {
|
4
|
+
if (id && className) {
|
5
|
+
d3.select("#"+id).classed(className, enabled);
|
6
|
+
}
|
7
|
+
}
|
8
|
+
|
9
|
+
function displayRelations(datum, nodeClass, enabled) {
|
10
|
+
if (!datum)
|
11
|
+
return;
|
12
|
+
// highlight current node (datum.id)
|
13
|
+
markClass(datum.id, nodeClass, enabled);
|
14
|
+
// highlight related nodes
|
15
|
+
for(var n in datum) {
|
16
|
+
if(n != "desc" && n != "id") {
|
17
|
+
markClass(datum[n], n, enabled);
|
18
|
+
}
|
19
|
+
}
|
20
|
+
// update node description
|
21
|
+
// since there are node and edge descriptions, have multiple #desc divs
|
22
|
+
var desc = enabled ? (datum.desc || "") : "";
|
23
|
+
d3.select("#desc ."+nodeClass).html(desc);
|
24
|
+
}
|
25
|
+
|
26
|
+
// store attributes in dot's tooltip attribute
|
27
|
+
// data associated with each node will split those up
|
28
|
+
function extractDatum(node, datum) {
|
29
|
+
var info = d3.select(node).select("a");
|
30
|
+
if (info && info[0] && info[0][0]) {
|
31
|
+
info = info.attr("xlink:title");
|
32
|
+
} else {
|
33
|
+
info = null;
|
34
|
+
}
|
35
|
+
if (info) {
|
36
|
+
var attrs = info.split("|");
|
37
|
+
// class:id:filename - class = (config|client|server|client_config|server_config)
|
38
|
+
for(var attr_num = 0; attr_num < attrs.length ; attr_num++) {
|
39
|
+
var attr = attrs[attr_num].trim();
|
40
|
+
attr_parts = attr.split(":");
|
41
|
+
if (attr_parts[1]) {
|
42
|
+
// TODO: handle duplicate attributes?
|
43
|
+
datum[attr_parts[0]] = attr_parts[1];
|
44
|
+
// TODO: add specific support for 3rd parameter (tablename/filename)
|
45
|
+
// if (attr_parts[2])
|
46
|
+
// attr["filename"] = attr_parts[2];
|
47
|
+
} else {
|
48
|
+
datum["desc"] = attr_parts[0];
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
return datum;
|
53
|
+
}
|
54
|
+
|
55
|
+
/* work around dot not supporting attribute `class="x"` */
|
56
|
+
function hackClass(node, datum) {
|
57
|
+
if (datum["class"]) {
|
58
|
+
d3.select(node).classed(datum["class"], true);
|
59
|
+
delete datum["class"];
|
60
|
+
}
|
61
|
+
return datum;
|
62
|
+
}
|
63
|
+
|
64
|
+
d3.selectAll(".node").datum(function() {
|
65
|
+
return hackClass(this, extractDatum(this, {"id" : this.id}));
|
66
|
+
})
|
67
|
+
.on('mouseover', function(datum) {
|
68
|
+
displayRelations(datum, 'active', true);
|
69
|
+
})
|
70
|
+
.on('mouseout', function(datum) {
|
71
|
+
displayRelations(datum, 'active', false);
|
72
|
+
});
|
73
|
+
|
74
|
+
d3.selectAll(".edge").datum(function(){
|
75
|
+
return extractDatum(this, {"id" : this.id});
|
76
|
+
})
|
77
|
+
/* selecting an edge is difficult
|
78
|
+
* instead of relying upon mouseover/mouseout, making mouseover sticky
|
79
|
+
*/
|
80
|
+
.on('mouseover', function(datum) {
|
81
|
+
if (datum.id != current_edge.id) {
|
82
|
+
displayRelations(current_edge, 'src-dest', false);
|
83
|
+
current_edge = datum;
|
84
|
+
displayRelations(datum, 'src-dest', true);
|
85
|
+
}
|
86
|
+
});
|