diff --git a/.travis.yml b/.travis.yml index 8074dd7e..38d25319 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,13 +16,13 @@ before_install: - gem --version - gem install bundler - sudo apt-get update -qq +install: - wget -O phantomjs-2.1.1-linux-x86_64.tar.bz2 https://github.com/pepijnve/asciidoctor-diagram-build/blob/master/linux/x86_64/phantomjs-2.1.1-linux-x86_64.tar.bz2?raw=true - tar jxf phantomjs-2.1.1-linux-x86_64.tar.bz2 - export PHANTOMJS_2=$PWD/phantomjs-2.1.1-linux-x86_64/bin/phantomjs - wget -O phantomjs-1.9.8-linux-x86_64.tar.bz2 https://github.com/pepijnve/asciidoctor-diagram-build/blob/master/linux/x86_64/phantomjs-1.9.8-linux-x86_64.tar.bz2?raw=true - tar jxf phantomjs-1.9.8-linux-x86_64.tar.bz2 - export PHANTOMJS_19=$PWD/phantomjs-1.9.8-linux-x86_64/bin/phantomjs -install: - sudo apt-get install -qq graphviz - sudo apt-get install -qq python-gtk2 - pip install --user blockdiag actdiag seqdiag nwdiag diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 26f1e5fd..5594c5d8 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -5,6 +5,7 @@ Enhancements:: * Apply anti-aliasing to blockdiag generated images + * Issue #118: Add support for Erd. Bug Fixes:: diff --git a/README.adoc b/README.adoc index 7a4bbd2e..73528afd 100644 --- a/README.adoc +++ b/README.adoc @@ -19,6 +19,7 @@ ifndef::env-site[:status:] :uri-blockdiag: http://blockdiag.com :uri-ditaa: http://ditaa.sourceforge.net/ :uri-dot: http://www.graphviz.org/content/dot-language +:uri-erd: https://github.com/BurntSushi/erd :uri-graphviz: http://www.graphviz.org :uri-imagemagick: http://www.imagemagick.org :uri-java: http://java.sun.com @@ -108,6 +109,7 @@ The following diagram types and output formats are available: |{uri-actdiag}[actdiag] | |{check}|{check}| |{uri-blockdiag}[blockdiag] | |{check}|{check}| |{uri-ditaa}[ditaa] | |{check}| | +|{uri-erd}[erd] | |{check}|{check}| |{uri-dot}[graphviz] | |{check}|{check}| |<> |{check}|{check}| | |{uri-mermaid}[mermaid] | |{check}|{check}| @@ -229,7 +231,7 @@ or load and register each extension individually. require 'asciidoctor-diagram/' ---- -`` can be one of `blockdiag`, `ditaa`, `graphviz`, `meme`, `mermaid`, `plantuml`, `shaape`, or `wavedrom`. +`` can be one of `blockdiag`, `ditaa`, `erd`, `graphviz`, `meme`, `mermaid`, `plantuml`, `shaape`, or `wavedrom`. Requiring one or more of these files will automatically register the extensions for all processed documents. @@ -275,6 +277,7 @@ The following table lists the tools that are required for each diagram type, the |actdiag |{uri-actdiag}[ActDiag] |`actdiag` |blockdiag |{uri-blockdiag}[BlockDiag] |`blockdiag` |ditaa |{uri-java}[Java] |`java` + |erd |{uri-erd}[Erd] |`erd` |graphviz |{uri-graphviz}[GraphViz] |`dot` or `graphvizdot` |meme |{uri-imagemagick}[ImageMagick] |`convert` and `identify` |mermaid |{uri-mermaid}[Mermaid] |`mermaid` diff --git a/lib/asciidoctor-diagram.rb b/lib/asciidoctor-diagram.rb index c6170c66..00ae25dc 100644 --- a/lib/asciidoctor-diagram.rb +++ b/lib/asciidoctor-diagram.rb @@ -1,5 +1,6 @@ require_relative 'asciidoctor-diagram/blockdiag' require_relative 'asciidoctor-diagram/ditaa' +require_relative 'asciidoctor-diagram/erd' require_relative 'asciidoctor-diagram/graphviz' require_relative 'asciidoctor-diagram/meme' require_relative 'asciidoctor-diagram/mermaid' diff --git a/lib/asciidoctor-diagram/erd.rb b/lib/asciidoctor-diagram/erd.rb new file mode 100644 index 00000000..cd39c405 --- /dev/null +++ b/lib/asciidoctor-diagram/erd.rb @@ -0,0 +1,7 @@ +require_relative 'extensions' + +Asciidoctor::Extensions.register do + require_relative 'erd/extension' + block Asciidoctor::Diagram::ErdBlockProcessor, :erd + block_macro Asciidoctor::Diagram::ErdBlockMacroProcessor, :erd +end diff --git a/lib/asciidoctor-diagram/erd/extension.rb b/lib/asciidoctor-diagram/erd/extension.rb new file mode 100644 index 00000000..b7809517 --- /dev/null +++ b/lib/asciidoctor-diagram/erd/extension.rb @@ -0,0 +1,42 @@ +require_relative '../extensions' +require_relative '../util/cli_generator' +require_relative '../util/platform' +require_relative '../util/which' + +module Asciidoctor + module Diagram + # @private + module Erd + include Which + + def self.included(mod) + [:png, :svg].each do |f| + mod.register_format(f, :image) do |parent, source| + erd(parent, source, f) + end + end + end + + def erd(parent, source, format) + erd_path = which(parent, 'erd') + dot_path = which(parent, 'dot', :alt_attrs => ['graphvizdot']) + + dot_code = CliGenerator.generate_stdin(erd_path, format.to_s, source.to_s) do |tool_path, output_path| + [tool_path, '-o', Platform.native_path(output_path), '-f', 'dot'] + end + + CliGenerator.generate_stdin(dot_path, format.to_s, dot_code) do |tool_path, output_path| + [tool_path, "-o#{Platform.native_path(output_path)}", "-T#{format.to_s}"] + end + end + end + + class ErdBlockProcessor < Extensions::DiagramBlockProcessor + include Erd + end + + class ErdBlockMacroProcessor < Extensions::DiagramBlockMacroProcessor + include Erd + end + end +end diff --git a/spec/erd_spec.rb b/spec/erd_spec.rb new file mode 100644 index 00000000..31052959 --- /dev/null +++ b/spec/erd_spec.rb @@ -0,0 +1,289 @@ +require_relative 'test_helper' + +code = <<-eos +title {label: "nfldb Entity-Relationship diagram (condensed)", size: "20"} + +# Entities + +[player] {bgcolor: "#d0e0d0"} + *player_id {label: "varchar, not null"} + full_name {label: "varchar, null"} + team {label: "varchar, not null"} + position {label: "player_pos, not null"} + status {label: "player_status, not null"} + +[team] {bgcolor: "#d0e0d0"} + *team_id {label: "varchar, not null"} + city {label: "varchar, not null"} + name {label: "varchar, not null"} + +[game] {bgcolor: "#ececfc"} + *gsis_id {label: "gameid, not null"} + start_time {label: "utctime, not null"} + week {label: "usmallint, not null"} + season_year {label: "usmallint, not null"} + season_type {label: "season_phase, not null"} + finished {label: "boolean, not null"} + home_team {label: "varchar, not null"} + home_score {label: "usmallint, not null"} + away_team {label: "varchar, not null"} + away_score {label: "usmallint, not null"} + +[drive] {bgcolor: "#ececfc"} + *+gsis_id {label: "gameid, not null"} + *drive_id {label: "usmallint, not null"} + start_field {label: "field_pos, null"} + start_time {label: "game_time, not null"} + end_field {label: "field_pos, null"} + end_time {label: "game_time, not null"} + pos_team {label: "varchar, not null"} + pos_time {label: "pos_period, null"} + +[play] {bgcolor: "#ececfc"} + *+gsis_id {label: "gameid, not null"} + *+drive_id {label: "usmallint, not null"} + *play_id {label: "usmallint, not null"} + time {label: "game_time, not null"} + pos_team {label: "varchar, not null"} + yardline {label: "field_pos, null"} + down {label: "smallint, null"} + yards_to_go {label: "smallint, null"} + +[play_player] {bgcolor: "#ececfc"} + *+gsis_id {label: "gameid, not null"} + *+drive_id {label: "usmallint, not null"} + *+play_id {label: "usmallint, not null"} + *+player_id {label: "varchar, not null"} + team {label: "varchar, not null"} + +[meta] {bgcolor: "#fcecec"} + version {label: "smallint, null"} + season_type {label: "season_phase, null"} + season_year {label: "usmallint, null"} + week {label: "usmallint, null"} + +# Relationships + +player *--1 team +game *--1 team {label: "home"} +game *--1 team {label: "away"} +drive *--1 team +play *--1 team +play_player *--1 team + +game 1--* drive +game 1--* play +game 1--* play_player + +drive 1--* play +drive 1--* play_player + +play 1--* play_player + +player 1--* play_player +eos + +describe Asciidoctor::Diagram::ErdBlockMacroProcessor, :broken_on_travis, :broken_on_windows do + it "should generate PNG images when format is set to 'png'" do + File.write('erd.txt', code) + + doc = <<-eos += Hello, Erd! +Doc Writer + +== First Section + +erd::erd.txt[format="png"] + eos + + d = load_asciidoc doc + expect(d).to_not be_nil + + b = d.find { |bl| bl.context == :image } + expect(b).to_not be_nil + + expect(b.content_model).to eq :empty + + target = b.attributes['target'] + expect(target).to_not be_nil + expect(target).to match(/\.png$/) + expect(File.exist?(target)).to be true + + expect(b.attributes['width']).to_not be_nil + expect(b.attributes['height']).to_not be_nil + end + + it "should generate SVG images when format is set to 'svg'" do + File.write('erd.txt', code) + + doc = <<-eos += Hello, Erd! +Doc Writer + +== First Section + +erd::erd.txt[format="svg"] + eos + + d = load_asciidoc doc + expect(d).to_not be_nil + + b = d.find { |bl| bl.context == :image } + expect(b).to_not be_nil + + expect(b.content_model).to eq :empty + + target = b.attributes['target'] + expect(target).to_not be_nil + expect(target).to match(/\.svg/) + expect(File.exist?(target)).to be true + + expect(b.attributes['width']).to_not be_nil + expect(b.attributes['height']).to_not be_nil + end +end + +describe Asciidoctor::Diagram::ErdBlockProcessor, :broken_on_travis, :broken_on_windows do + it "should generate PNG images when format is set to 'png'" do + doc = <<-eos += Hello, Erd! +Doc Writer + +== First Section + +[erd, format="png"] +---- +#{code} +---- + eos + + d = load_asciidoc doc + expect(d).to_not be_nil + + b = d.find { |bl| bl.context == :image } + expect(b).to_not be_nil + + expect(b.content_model).to eq :empty + + target = b.attributes['target'] + expect(target).to_not be_nil + expect(target).to match(/\.png$/) + expect(File.exist?(target)).to be true + + expect(b.attributes['width']).to_not be_nil + expect(b.attributes['height']).to_not be_nil + end + + it "should generate SVG images when format is set to 'svg'" do + doc = <<-eos += Hello, Erd! +Doc Writer + +== First Section + +[erd, format="svg"] +---- +#{code} +---- + eos + + d = load_asciidoc doc + expect(d).to_not be_nil + + b = d.find { |bl| bl.context == :image } + expect(b).to_not be_nil + + expect(b.content_model).to eq :empty + + target = b.attributes['target'] + expect(target).to_not be_nil + expect(target).to match(/\.svg/) + expect(File.exist?(target)).to be true + + expect(b.attributes['width']).to_not be_nil + expect(b.attributes['height']).to_not be_nil + end + + it "should raise an error when when format is set to an invalid value" do + doc = <<-eos += Hello, Erd! +Doc Writer + +== First Section + +[erd, format="foobar"] +---- +---- + eos + + expect { load_asciidoc doc }.to raise_error(/support.*format/i) + end + + it "should not regenerate images when source has not changed" do + File.write('erd.txt', code) + + doc = <<-eos += Hello, Erd! +Doc Writer + +== First Section + +erd::erd.txt + +[erd, format="png"] +---- +#{code} +---- + eos + + d = load_asciidoc doc + b = d.find { |bl| bl.context == :image } + expect(b).to_not be_nil + target = b.attributes['target'] + mtime1 = File.mtime(target) + + sleep 1 + + d = load_asciidoc doc + + mtime2 = File.mtime(target) + + expect(mtime2).to eq mtime1 + end + + it "should handle two block macros with the same source" do + File.write('erd.txt', code) + + doc = <<-eos += Hello, Erd! +Doc Writer + +== First Section + +erd::erd.txt[] +erd::erd.txt[] + eos + + load_asciidoc doc + expect(File.exist?('erd.png')).to be true + end + + it "should respect target attribute in block macros" do + File.write('erd.txt', code) + + doc = <<-eos += Hello, Erd! +Doc Writer + +== First Section + +erd::erd.txt["foobar"] +erd::erd.txt["foobaz"] + eos + + load_asciidoc doc + expect(File.exist?('foobar.png')).to be true + expect(File.exist?('foobaz.png')).to be true + expect(File.exist?('erd.png')).to be false + end +end \ No newline at end of file diff --git a/spec/test_helper.rb b/spec/test_helper.rb index 80955baf..511aed87 100644 --- a/spec/test_helper.rb +++ b/spec/test_helper.rb @@ -8,6 +8,7 @@ require_relative '../lib/asciidoctor-diagram' require_relative '../lib/asciidoctor-diagram/blockdiag/extension' require_relative '../lib/asciidoctor-diagram/ditaa/extension' +require_relative '../lib/asciidoctor-diagram/erd/extension' require_relative '../lib/asciidoctor-diagram/graphviz/extension' require_relative '../lib/asciidoctor-diagram/meme/extension' require_relative '../lib/asciidoctor-diagram/mermaid/extension' @@ -62,6 +63,10 @@ def load_asciidoc(source, options = {}) c.filter_run_excluding :broken_on_windows => true end + if ENV['TRAVIS'] + c.filter_run_excluding :broken_on_travis => true + end + TEST_DIR = File.expand_path('testing') c.before(:suite) do