""" 2009-05-20: created by bousch_AT_gmail.com Do with it what you want, no warranty whatsoever. Changes: 2009-06-20: fixed error when exporting to directory with spaces in name. Description: Enables you to insert Groff fragment in your pages. Install: Save this file to your WikidPad/extensions folder Dependencies: Groff ImageMagick Arguments: groff: the "-e -p" arguments are passed to Groff by default. If you want extra arguments you can pass them with the 'args=' string ImageMagick: You can pass arguments to the convert command with the 'iargs=' string Example: [:groff:/// .EQ delim $$ .EN .PS arrow box color "darkgreen" outline "white" "$space 0 {H( omega )} over {1 - H( omega )}$" arrow .PE ///;'iargs=-trim -density 150'] """ import os, os.path from subprocess import list2cmdline import time import wx from pwiki.TempFileSet import createTempFile from pwiki.StringOps import mbcsEnc, mbcsDec, lineendToOs WIKIDPAD_PLUGIN = (("InsertionByKey", 1), ("Options", 1)) LOG = False def describeInsertionKeys(ver, app): """ API function for "InsertionByKey" plugins Returns a sequence of tuples describing the supported insertion keys. Each tuple has the form (insKey, exportTypes, handlerFactory) where insKey is the insertion key handled, exportTypes is a sequence of strings describing the supported export types and handlerFactory is a factory function (normally a class) taking the wxApp object as parameter and returning a handler object fulfilling the protocol for "insertion by key" (see EqnHandler as example). ver -- API version (can only be 1 currently) app -- wxApp object """ return ( (u"groff", ("html_single", "html_previewWX", "html_preview", "html_multi"), GroffHandler), ) class GroffHandler: """ Base class fulfilling the "insertion by key" protocol. """ def __init__(self, app): self.app = app self.extAppExe = None def taskStart(self, exporter, exportType): """ This is called before any call to createContent() during an export task. An export task can be a single HTML page for preview or a single page or a set of pages for export. exporter -- Exporter object calling the handler exportType -- string describing the export type Calls to createContent() will only happen after a call to taskStart() and before the call to taskEnd() """ # Find Gnuplot executable by configuration setting self.extAppExe = self.app.getGlobalConfig().get("main", "plugin_groff_exePath", "") self.imExtAppExe = self.app.getGlobalConfig().get("main", "plugin_imagemagick_dirPath", "") def taskEnd(self): """ Called after export task ended and after the last call to createContent(). """ pass def createContent(self, exporter, exportType, insToken): """ Handle an insertion and create the appropriate content. exporter -- Exporter object calling the handler exportType -- string describing the export type insToken -- insertion token to create content for (see also PageAst.Insertion) An insertion token has the following member variables: key: insertion key (unistring) value: value of an insertion (unistring) appendices: sequence of strings with the appendices Meaning and type of return value is solely defined by the type of the calling exporter. For HtmlXmlExporter a unistring is returned with the HTML code to insert instead of the insertion. """ if not insToken.value: # Nothing in, nothing out return u"" if self.extAppExe == "": # No path to groff executable -> show message return u"
" + _(u"[Please set path to groff executable]") +\
                    u"
" if self.imExtAppExe == "": return u"
" + _(u"[Please set path to imageMagick executable]") +\
                    u"
" # Get exporters temporary file set (manages creation and deletion of # temporary files) tfs = exporter.getTempFileSet() pythonUrl = (exportType != "html_previewWX") dstPSFullPath = tfs.createTempFile("", ".ps", relativeTo="") dstPSFullPath = dstPSFullPath.replace ( '\\','/') dstFullPath = tfs.createTempFile("", ".png", relativeTo="") dstFullPath = dstFullPath.replace ( '\\','/') url = tfs.getRelativeUrl(None, dstFullPath, pythonUrl=pythonUrl) baseDir = os.path.dirname(exporter.getMainControl().getWikiConfigPath()) # find arguments in appendices groffPattern = 'args=' imPattern = 'iargs=' groffArguments = '' convertArguments = '' for a in insToken.appendices: if a.startswith(groffPattern): groffArguments = str(a[len(groffPattern):]) if a.startswith(imPattern): convertArguments = str(a[len(imPattern):]) # Retrieve quoted content of the insertion #bstr = lineendToOs(mbcsEnc(insToken.value, "replace")[0]) # windows lineendings confuse pic2plot bstr = mbcsEnc(insToken.value, "replace")[0] # Store token content in a temporary file srcfilepath = createTempFile(bstr, ".ms") srcfilepath = srcfilepath.replace ( '\\','/') try: cmdline = '%s -t -e -p %s "%s" > "%s"' % (self.extAppExe, groffArguments, srcfilepath, dstPSFullPath) # Run external application from subprocess import Popen, PIPE p = Popen(cmdline, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=False, cwd=os.path.dirname(self.extAppExe)) p.wait() # Will wait for process to end childIn, childOut, childErr = (p.stdin, p.stdout, p.stderr) # convert ps to png convertcmd = '%s %s "%s" "%s"' % (os.path.join(self.imExtAppExe, 'convert'), convertArguments, dstPSFullPath, dstFullPath) p2 = Popen(convertcmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=False) p2.wait() childIn2, childOut2, childErr2 =(p2.stdin, p2.stdout, p2.stderr) if u"noerror" in [a.strip() for a in insToken.appendices]: childErr.read() errResponse = "" else: errResponse = childErr.read() finally: os.unlink(srcfilepath) # Open logfile if LOG: f = open(baseDir + '\groff.log', 'a') f.write(time.strftime('\n%d-%m-%Y %H:%M:%S\n')) f.write("Current dir: %s\n" % os.getcwd()) f.write("Commandline: %s\n" % cmdline) f.write(time.strftime('\n%d-%m-%Y %H:%M:%S') + ' : ' + errResponse) f.write("Convert: %s\n" % convertcmd) f.write("Appendices:\n") for a in insToken.appendices: appendices = a.strip().split("---") for app in appendices: f.write(app.strip() + "\n") f.close() if errResponse != "": errResponse = mbcsDec(errResponse, "replace")[0] return u"
" + _(u"[Groff error: %s]") % errResponse +\
                    u"
" # Return appropriate HTML code for the image if exportType == "html_previewWX": # Workaround for internal HTML renderer return (u'groff' u' ') % url else: return u'groff' \ % url def getExtraFeatures(self): """ Returns a list of bytestrings describing additional features supported by the plugin. Currently not specified further. """ return () def registerOptions(ver, app): """ API function for "Options" plugins Register configuration options and their GUI presentation ver -- API version (can only be 1 currently) app -- wxApp object """ # Register option app.getDefaultGlobalConfigDict()[("main", "plugin_groff_exePath")] = u"" app.getDefaultGlobalConfigDict()[("main", "plugin_imagemagick_dirPath")] = u"" # Register panel in options dialog app.addOptionsDlgPanel(GroffOptionsPanel, u" Groff") class GroffOptionsPanel(wx.Panel): def __init__(self, parent, optionsDlg, app): """ Called when "Options" dialog is opened to show the panel. Transfer here all options from the configuration file into the text fields, check boxes, ... """ wx.Panel.__init__(self, parent) self.app = app pt = self.app.getGlobalConfig().get("main", "plugin_groff_exePath", "") self.tfPath = wx.TextCtrl(self, -1, pt) mainsizer = wx.BoxSizer(wx.VERTICAL) inputsizer = wx.BoxSizer(wx.HORIZONTAL) inputsizer.Add(wx.StaticText(self, -1, _(u"Path to Groff:")), 0, wx.ALL | wx.EXPAND, 5) inputsizer.Add(self.tfPath, 1, wx.ALL | wx.EXPAND, 5) mainsizer.Add(inputsizer, 0, wx.EXPAND) pt = self.app.getGlobalConfig().get("main", "plugin_imagemagick_dirPath", "") self.ifPath = wx.TextCtrl(self, -1, pt) inputsizer = wx.BoxSizer(wx.HORIZONTAL) inputsizer.Add(wx.StaticText(self, -1, _(u"Path to ImageMagick folder:")), 0, wx.ALL | wx.EXPAND, 5) inputsizer.Add(self.ifPath, 1, wx.ALL | wx.EXPAND, 5) mainsizer.Add(inputsizer, 0, wx.EXPAND) self.SetSizer(mainsizer) self.Fit() def setVisible(self, vis): """ Called when panel is shown or hidden. The actual wxWindow.Show() function is called automatically. If a panel is visible and becomes invisible because another panel is selected, the plugin can veto by returning False. When becoming visible, the return value is ignored. """ return True def checkOk(self): """ Called when "OK" is pressed in dialog. The plugin should check here if all input values are valid. If not, it should return False, then the Options dialog automatically shows this panel. There should be a visual indication about what is wrong (e.g. red background in text field). Be sure to reset the visual indication if field is valid again. """ return True def handleOk(self): """ This is called if checkOk() returned True for all panels. Transfer here all values from text fields, checkboxes, ... into the configuration file. """ pt = self.tfPath.GetValue() self.app.getGlobalConfig().set("main", "plugin_groff_exePath", pt) pt = self.ifPath.GetValue() self.app.getGlobalConfig().set("main", "plugin_imagemagick_dirPath", pt)