Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added embedding UML diagrams and images in HTML #24

Open
tysenmoore-xse opened this issue Oct 29, 2012 · 3 comments · May be fixed by #94
Open

Added embedding UML diagrams and images in HTML #24

tysenmoore-xse opened this issue Oct 29, 2012 · 3 comments · May be fixed by #94

Comments

@tysenmoore-xse
Copy link

Steve,

I have added a new set of features to the LDoc code and wanted to pass them on for integration assuming you find it as useful as I do. I have added the ability to embed images or generate UML diagrams and embed the image in the document.

The UML can be part of a LDoc section using the syntax from plantUml or various other text to UML parsers. I setup plantUml as the default. This allows the user to embed nice UML images in the generated documents by using simple text within you code comments. For example embedding a UML sequence diagram by using:

--- @startuml
--- a->b:test request
--- a<-b:test response
--- @enduml

I have added the ability to alter some of the default capability by adding some JSON text after the "@startuml" tag. The options are:

-- "exec":"path"         

-- This allows you to alter the UML generation engine and path for execution. You can specify a different path/executable for the UML parser. For example,
"exec":"/usr/bin/msvc %s"
NOTE: The "%s" is where the temp UML text will be placed (within a temp file). The default is "plantuml %s".

-- "removeTags":true

-- if true (no quotes), the @startuml and @enduml are removed, this makes it possible to support other UML parsers that do not use these tags. The default is false.

-- "fileType":"gif"

-- This defines a different file type that is generated by the UML parsers. The default is "png", but you can specify a "gif" (for example) if the UML parser generates a png.

Again, I have tested with plantUml but have hopefully left it flexible enough to use different UML parsers.

I also added the ability to embed images in your generated documents. To embed an image you add:
@embed_XXX{"/path/to/file.XXX"} XXX specifies the extension type. For example,

@embed_gif{"tmp/test.gif"}

This will embed the base64 image in the document.

This does add a dependency to Lua sockets. However, if the module is not found it will just report the error.

I have tried to follow the original intent of the code. I preprocess the text prior to extracting tags. So there is no use for adding tags or anything. This way the original tag handling stays intact. I'd love to see this as part of the latest source code. Please let me know what you think.

Tysen Moore

Diff is:

diff --git a/ldoc/parse.lua b/ldoc/parse.lua
index 5bebc3a..28102f1 100644
--- a/ldoc/parse.lua
+++ b/ldoc/parse.lua
@@ -6,6 +6,21 @@ local tools = require 'ldoc.tools'
 local doc = require 'ldoc.doc'
 local Item,File = doc.Item,doc.File

+-- This functionality is only needed for UML support.
+-- If this does not load it will only trigger a failure
+-- if the UML syntax was detected.
+local bOk, http   = pcall( require, "socket.http")
+local mime        = nil
+if bOk == false then
+   http = nil
+else
+   bOk, mime   = pcall( require, "mime")
+   if bOk == false then
+      mime = nil
+   end
+end
+
+
 ------ Parsing the Source --------------
 -- This uses the lexer from PL, but it should be possible to use Peter Odding's
 -- excellent Lpeg based lexer instead.
@@ -46,6 +61,138 @@ function parse_tags(text)
    return preamble,tag_items
 end

+-- Used to preprocess the tag text prior to extracting
+-- tags.  This allows us to replace tags with other text.
+-- For example, we embed images into the document.
+local function preprocess_tag_strings( s )
+
+   local function create_embedded_image( filename, fileType )
+
+      local html = ""
+
+      if mime == nil then
+         local errStr = "LDoc error, Lua socket/mime module needed for UML"
+         -- Just log the error in the doc
+         html = "<b><u>"..errStr.."</u></b>"
+         print(errStr)
+         return html
+      end
+
+      if fileType == nil then
+         fileType = "png"
+      end
+
+      -- Now open the new image file and embed it
+      -- into the text as an HTML image
+      local fp = io.open( filename, "r" )
+      if fp then
+         -- This could be more efficient instead of
+         -- reading all since the definitions are
+         -- typically small this will work for now
+         local img = fp:read("*all")
+         fp:close()
+
+         html = string.format( '<img src="data:image/%s;base64,%s" />', fileType, mime.b64( img ) )
+      else
+         local errStr = string.format("LDoc error opening %s image file: %q", fileType, filename)
+         -- Just log the error in the doc
+         html = "<br><br><b><u>"..errStr.."</u></b><br><br>"
+         print(errStr)
+      end
+
+      return html
+   end
+
+   ----------------------------------------------------------
+   -- Embedded UML
+   ------------------
+   local epos
+   local execPath = "plantuml %s"
+   local spos     = string.find(s, "@startuml")
+   if spos then
+      _, epos = string.find(s, "@enduml")
+   end
+
+   if spos and epos then
+
+      local filename = os.tmpname()
+      local sUml     = string.sub(s,spos,epos) -- UML definition text
+
+      -- Grab the text before and after the UML definition
+      local preStr   = string.match(s, "(.*)@startuml")
+      local postStr  = string.match(s, "@enduml(.*)")
+      local fileType = "png"
+      local fp       = io.open( filename, "w" )
+      local html     = ""
+
+      --Add support for optional formatting in a json format
+      if string.sub( sUml, 10,10 ) == "{" then
+         local sFmt = string.match( sUml, ".*{(.*)}" )
+
+         -- Remove the formatter
+         sUml = string.gsub( sUml, ".*}", "@startuml" )
+
+         -- To avoid adding the dependency of JSON we will
+         -- parse what we need.
+
+         -- "exec":"path"
+         -- This allows you to alter the UML generation engine and path for execution
+         execPath = string.match(sFmt, '.-"exec"%s-:%s-"(.*)".-') or execPath
+
+         -- "removeTags":true
+         -- if true, the @startuml and @enduml are removed, this
+         -- makes it possible to support other UML parsers.
+         sRemoveTags = string.match(sFmt, '.-"removeTags"%s-:%s-(%a*).-')
+         if sRemoveTags == "true" then
+            sUml = string.gsub( sUml, "^%s*@startuml", "" )
+            sUml = string.gsub( sUml, "@enduml%s*$", "" )
+         end
+
+         -- "fileType":"gif"
+         -- This defines a different file type that is generated by
+         -- the UML parsers.
+         fileType = string.match(sFmt, '.-"fileType"%s-:%s-"(.*)".-') or fileType
+      end
+
+      if fp then
+         -- write the UML text to a file
+         fp:write( sUml )
+         fp:close()
+
+         -- create the diagram, overwrites the existing file
+         os.execute( string.format(execPath, filename ) )
+
+         -- create the embedded text for the image
+         html = create_embedded_image( filename, fileType )
+
+         os.remove( filename ) -- this is the PNG from plantUml
+      else
+         local errStr = "LDoc error creating UML temp file"
+         -- Just log the error in the doc
+         html = "<br><br><b><u>"..errStr.."</u></b><br><br>"
+         print(errStr)
+      end
+      s = preStr..html..postStr
+
+   end -- embed UML
+
+   ----------------------------------------------------------
+   -- Embedded Image
+   ------------------
+   local fileType, filename = string.match(s, '@embed_(.*){"(.*)"}')
+   if fileType and filename then
+
+      -- create the embedded text for the image
+      html = create_embedded_image( filename, fileType )
+
+      s = string.gsub(s, "@embed_.*{.*}", html)
+
+   end -- embedded image
+
+   return s
+
+end -- preprocess_tag_strings
+
 -- This takes the collected comment block, and uses the docstyle to
 -- extract tags and values.  Assume that the summary ends in a period or a question
 -- mark, and everything else in the preamble is the description.
@@ -53,6 +200,9 @@ end
 -- Alias substitution and @TYPE NAME shortcutting is handled by Item.check_tag
 local function extract_tags (s)
    if s:match '^%s*$' then return {} end
+
+   s = preprocess_tag_strings( s )
+
    local preamble,tag_items = parse_tags(s)
    local strip = tools.strip
    local summary, description = preamble:match('^(.-[%.?])(%s.+)')
@stevedonovan
Copy link
Contributor

On Mon, Oct 29, 2012 at 5:43 AM, Tysen Moore notifications@github.com wrote:

I have added a new set of features to the LDoc code and wanted to pass them on for integration assuming you find it as useful as I do. I have added the ability to embed images or generate UML diagrams and embed the image in the document.

Thanks, Tysen - this does seem like generally useful functionality. I
think that @embed{'images/test.gif'} would be sufficiently
unambiguous, since we need the extension anyway.

This will embed the base64 image in the document.

Why not simply put all images in a standard subdir of the output directory?

comments. For example embedding a UML sequence diagram by using:
--- @startuml
--- a->b:test request
--- a<-b:test response
--- @enduml

That seems clear. These tags do behave differently from the others,
and in fact you'd probably only need a single tag @uml and end its
contents in the same way as with @param, that is by finding any other
tag or end-of-comment.

I have added the ability to alter some of the default capability by adding some JSON text after the "@startuml" tag.

Since these would probably apply for the whole project, I think that
the parameters should be specified in the config.lua file.

Interesting possibilties:

  • cache the results of processing the text, so you can ship the
    whole project without necessarily requiring your users to have the
    whole toolchain.
  • allow for other formats such graphviz' dot files. Your scheme
    does allow that, but then the word 'uml' is too specific?

Let me think about this, but yes definitely this is something that
people will find useful!

steve d.

@tysenmoore-xse
Copy link
Author

Steve,

Thanks for the feedback. Some comments:

  1. I agree about the "@embed" only. I made that same connection when I was typing up this issue. Missed it the first time around.
  2. It would also be nice to support just the image creation. I added the embedding of images because I have sequence diagrams defined in my function and module comments to better explain their use. I find it useful at times to send a single document to various people.

I have forked so I can put up my changes. As I refine this I can check out on my branch. Keep me posted on your thoughts.

@tysenmoore-xse
Copy link
Author

Steve,

I posted my code to my forked project. The following changes are:

  1. I modified the @embed so you don't specify the extension.
  2. I added more options to the @startuml to support keeping a cahced copy of the generated image.
  3. Added some documentation to doc.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

3 participants