diff --git a/change.txt b/change.txt new file mode 100644 index 00000000..c4d0f048 --- /dev/null +++ b/change.txt @@ -0,0 +1,175 @@ +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 = ""..errStr.."" ++ 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( '', 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 = "

"..errStr.."

" ++ 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 = "

"..errStr.."

" ++ 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.+)') diff --git a/doc/doc.md b/doc/doc.md index 8d402c50..404496dd 100644 --- a/doc/doc.md +++ b/doc/doc.md @@ -451,6 +451,57 @@ a current limitation is that the master module must be processed before the subm See the `tests/submodule` example for how this works in practice. +## Embedding Images in the Document + +LDoc supports the ability of embedding various images within the generated document--not just a simple reference. Within a LDoc section use the following syntax: + +`@embed{"filename"}` + + +NOTE: The filename must contain the a file extension that matches the file type. For example, ".png" for a PNG file format. + + +## Embed UML Diagrams in the Document + +LDoc supports the ability to define UML diagrams using a textual format and create graphical UML files that can be cached or embedded in the generated document itself. The current LDoc mainly supports the [plantUML](http://plantuml.sourceforge.net/) textual format. The syntax is however flexible enough to support other UML textual formats/generation. The syntax is: + + --- a simple example. + -- @startuml + -- a->b : sample request + -- a<-b : sample response + -- @enduml + +The `@startuml` also supports various parameters that allow you to customize where and how the UML is generated. The parameters follow the `@startuml` in a JSON format. The supported options are: + +### "exec":"path/filename" +This allows you to alter the UML generation engine and path for execution. You can specify a different path/executable and options for the UML parser. For example, + +`"exec":"/usr/bin/msvc %s"` + +NOTE: The "%s" is where the temp [file] UML text will be placed. The default is "plantuml %s". + +### "removeTags":true +If true (no quotes), the @startuml and @enduml are removed prior to sending to the parser. This option 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. This does not alter what if generated, but it tells LDoc what to expect out of the parser. The default is "png", but you can specify a "gif" (for example) if the UML parser generates a gif. + +### "cacheFile":"path/filename" +This allows you to define a location where you want to cache a local copy of the image. This results in the file being created at this location and by default it is NOT embedded in the generated diagram. You can still keep a cached copy and embed the image by using "forceEmbed". + +### "forceEmbed":true +If true this overrides the cacheFile options default for not embedding the image. This means that when using "cacheFile" and "forceEmbed" a file is cached AND embedded. + + --- Cache the file AND embed the file in the generated document. + -- @startuml{"cacheFile":"/path/to/cachedFile.png","forceEmbed":true} + -- a1->b1:test request + -- a1<-b1:test response + -- @enduml + +### "showSyntax":"left"|"right"|"above"|"below"|"none"[default] +By using this option you can specify that you also want the syntax to be shown: "left"|"right"|"above"|"below". "none" is the same as not specifying this option. + + ## Differences from LuaDoc LDoc only does 'module' documentation, so the idea of 'files' is redundant. diff --git a/ldoc/html/ldoc_ltp.lua b/ldoc/html/ldoc_ltp.lua index 957aae37..0d614da9 100644 --- a/ldoc/html/ldoc_ltp.lua +++ b/ldoc/html/ldoc_ltp.lua @@ -265,7 +265,6 @@ return [==[
-generated by LDoc 1.4.0
diff --git a/ldoc/parse.lua b/ldoc/parse.lua index 6889dfe8..14a3824b 100644 --- a/ldoc/parse.lua +++ b/ldoc/parse.lua @@ -10,6 +10,21 @@ local doc = require 'ldoc.doc' local Item,File = doc.Item,doc.File local unpack = utils.unpack +-- 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. @@ -121,6 +136,258 @@ function Tags:iter () return self._order:iter() 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 = ""..errStr.."" + 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, "rb" ) + 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( '', 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 = "

"..errStr.."

" + print(errStr) + end + + return html + end + + local function create_embedded_syntax( filename, syntaxStyle ) + + local html = "" + + -- Now open the new syntax file and create an HTML version + local fp = io.open( filename, "rb" ) + if fp then + + -- This could be more efficient instead of + -- reading all since the definitions are + -- typically small this will work for now + local uml = fp:read("*all") + fp:close() + + -- The '@' will trigger an error for unknown tag, + -- so just remove them, it will still be understood + uml = string.gsub(uml, "@startuml", "") + uml = string.gsub(uml, "@enduml", "") + + uml = string.gsub(uml, "&", "&") + uml = string.gsub(uml, "<", "<") + uml = string.gsub(uml, ">", ">") + uml = string.gsub(uml, ">", ">") + + -- TODO: Needs work because the style needs to be cleared + if syntaxStyle == "left" then + html = string.format( '
\n%s\n
', uml) + elseif syntaxStyle == "right" then + html = string.format( '
\n%s\n
', uml) + else + html = string.format( '
\n%s\n
', uml) + end + + else + local errStr = string.format("LDoc error opening UML file: %q", filename) + -- Just log the error in the doc + html = "

"..errStr.."

" + 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", spos+1) + end + + while spos and epos do + + local uml_filename = os.tmpname() + local img_filename = uml_filename + local sUml = string.sub(s,spos,epos) -- UML definition text + + -- Grab the text before and after the UML definition + local preStr = string.sub(s, 1, spos-1) + local postStr = string.sub(s, epos+1) + local fileType = "png" + local showSyntax = "none" + local fp = io.open( uml_filename, "w" ) + local html = "" + local cacheFileName = nil + local sEmbedImage = "true" + + --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 + -- Path should have a %s for filename placement. + 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 + + -- "cacheFile":"path" + -- specify where to save the image. This will NOT embed the image + -- but will save the file to this path/filename. + cacheFileName = string.match(sFmt, '.-"cacheFile"%s-:%s-"(.-)".-') + if cacheFileName then + -- by default we will not embed when image is cached + -- use "forceEmbed" to override this option + sEmbedImage = "false" + end + + -- "showSyntax":"left"|"right"|"above"|"below"|"none"[default] + -- By using this option you can specify that you also want the + -- syntax to be shown: "left"|"right"|"above"|"below". "none" + -- is the same as not specifying this option. + showSyntax = string.match(sFmt, '.-"showSyntax"%s-:%s-"(.-)".-') or showSyntax + + -- "forceEmbed":true + -- if true, this will still embed the image even if the "cacheFile" + -- option is enabled. This makes it possible to cache AND embed + -- the images. + sEmbedImage = string.match(sFmt, '.-"forceEmbed"%s-:%s-(%a*).-') or sEmbedImage + + end + + if fp then + -- write the UML text to a file + fp:write( sUml ) + fp:close() + + -- create the diagram, overwrites the existing file + -- This will generate a PNG filename + os.execute( string.format(execPath, uml_filename ) ) + + -- filename will now be the output name from the previous plantuml excecution + img_filename = string.format("%s.%s", uml_filename, (fileType or "png")) + + if cacheFileName then + + -- Save the image to a specific location which we + -- do not remove, nor embed in the HTML + os.rename( img_filename, cacheFileName) + + if sEmbedImage == "true" then + -- create the embedded text for the image + html = create_embedded_image( cacheFileName, fileType ) + end + + elseif sEmbedImage == "true" then + -- create the embedded text for the image + html = create_embedded_image( img_filename, fileType ) + + os.remove( img_filename ) -- this is the PNG from plantUml + + else + os.remove( img_filename ) -- this is the PNG from plantUml + end + + if showSyntax and showSyntax ~= "none" then + + local umlSyntax = create_embedded_syntax( uml_filename, showSyntax ) + + if showSyntax == "above" then + html = umlSyntax.."
"..html + elseif showSyntax == "below" then + html = html.."
"..umlSyntax + else + html = html..umlSyntax + end + + end + + os.remove( uml_filename ) -- this is the UML + + else + local errStr = "LDoc error creating UML temp file" + -- Just log the error in the doc + html = "

"..errStr.."

" + print(errStr) + end + + s = preStr..html..postStr + + spos = string.find(s, "@startuml", #preStr+#html+1) + if spos then + _, epos = string.find(s, "@enduml",spos+1) + end + + end -- embed UML + + ---------------------------------------------------------- + -- Embedded Image + ------------------ + local filename = string.match(s, '@embed{"(.-)"}') + while filename do + + local fileType = string.match(filename, "%.(.*)$") + + -- create the embedded text for the image + html = create_embedded_image( filename, fileType ) + + -- Replace the first occurance + s = string.gsub(s, "@embed{.-}", html, 1) + + filename = string.match(s, '@embed{"(.-)"}') + + 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. @@ -132,6 +399,7 @@ local function extract_tags (s,args) if args.colon then --and s:match ':%s' and not s:match '@%a' then preamble,tag_items = parse_colon_tags(s) else + s = preprocess_tag_strings( s ) preamble,tag_items = parse_at_tags(s) end local strip = tools.strip diff --git a/readme.md b/readme.md index 3d6c378c..0098a2a1 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,13 @@ Copyright (C) 2011-2012 Steve Donovan. +## Branch/Fork Rationale + +I've added the ability to: + + - embed or link images into the HTML + - embed plant UML sequence diagram syntax and generate the images and/or embed/link them into the HTML + ## Rationale This project grew out of the documentation needs of diff --git a/tests/run_umltest.sh b/tests/run_umltest.sh new file mode 100755 index 00000000..d0271991 --- /dev/null +++ b/tests/run_umltest.sh @@ -0,0 +1 @@ +ldoc -a -v -c ./uml_config.ld -t UML_Tests -d ./uml -o uml_test ./uml.lua diff --git a/tests/test.gif b/tests/test.gif new file mode 100644 index 00000000..e17dc671 Binary files /dev/null and b/tests/test.gif differ diff --git a/tests/uml.lua b/tests/uml.lua new file mode 100644 index 00000000..b8b1c707 --- /dev/null +++ b/tests/uml.lua @@ -0,0 +1,101 @@ +#!/usr/bin/env lua +------------------------------------------------------------------------------- +--- Test multiple simple UML diagrams in one comment
+--- +--- @startuml +--- a->b:test request +--- a<-b:test response +--- @enduml +--- +---
+--- +--- @startuml +--- a2->b2:test 2 request +--- a2<-b2:test 2 response +--- @enduml +---
+--- ----------------------------
+--- onEvent API:
+--- ----------------------------
+--- { "event" : EVENT_NAME, [EVENT_SPECIFIC_DATA] }
+---
+--- EVENT_NAME:
+---
+--- eos.........end of stream
+--- reason : info about the EOS

+--- +--- @copyright 2012 Company LLC +--- @license Company LLC Proprietary +--- +--- @module Lua_Binding +--- @alias g_dbMethods +------------------------------------------------------------------------------- + +---------------------------------------------------------------- +--- Test_SeqDiagramUseCase1 +--- +--- Test the sequence diagram use case +--- Nothing, then Nav Starts/Stops.
+---
+--- +--- @startuml{"cacheFile":".cachedFile.png","forceEmbed":true,"fileType":"png"} +--- a1->b1:test request +--- a1<-b1:test response +--- @enduml +--- +---
+--- +--- @embed{"./test.gif"} +---
+--- @embed{"./test.gif"} +--- +--- +---------------------------------------------------------------- +local function Test_SeqDiagramUseCase1() +end + +---------------------------------------------------------------- +--- Test_SeqDiagramUseCase2 +--- +--- Test the sequence diagram use case +--- Nothing, then Nav Starts/Stops.
+---
+---

ABOVE:
+--- @startuml{"showSyntax":"above"} +--- a1->b1:test request +--- a1<-b1:test response +--- @enduml +--- +---

BELOW:
+--- @startuml{"showSyntax":"below"} +--- a1->b1:test request +--- a1<-b1:test response +--- @enduml +--- +---

LEFT:
+--- @startuml{"showSyntax":"left"} +--- a1->b1:test request +--- a1<-b1:test response +--- @enduml +--- +---

RIGHT:
+--- @startuml{"showSyntax":"right"} +--- a1->b1:test request +--- a1<-b1:test response +--- @enduml +--- +---

NONE:
+--- @startuml{"showSyntax":"none"} +--- a1->b1:test request +--- a1<-b1:test response +--- @enduml +--- +---

not defined:
+--- @startuml +--- a1->b1:test request +--- a1<-b1:test response +--- @enduml +--- +---------------------------------------------------------------- +local function Test_SeqDiagramUseCase2() +end diff --git a/tests/uml_config.ld b/tests/uml_config.ld new file mode 100644 index 00000000..9b9df1b0 --- /dev/null +++ b/tests/uml_config.ld @@ -0,0 +1,4 @@ +format='plain' +project = 'Basic Example' +file = {'types.lua','classes.lua'} +topics = {'one.md','two.md'}