# # REDocument.py # ReSTedit # # Created by Dinu Gherman on Wed Jul 02 2003. # Copyright (c) 2003 Dinu Gherman. All rights reserved. # import os, time from os.path import dirname, basename, join, splitext from objc import YES, NO, accessor, selector from Foundation import * from AppKit import * from PyObjCTools import NibClassBuilder import ukkqueue import process from REWindowController import REWindowController, publish_string class REDocument(NSDocument): def init(self): self = super(REDocument, self).init() if self: self.windowController = None self.path = None self.sourceURL = None self.setContentFromString_("") self.useEmbeddedStyleSheet = YES self.kqueue = ukkqueue.UKKQueue.alloc().init() self.kqueue.setDelegate_(self) self.iv_availableFormats = ['ReST', 'Markdown'] self.iv_selectedFormat = "ReST" self.iv_selectedCSS = NSApp().delegate().availableStyleSheets()[0] return self def initWithContentsOfURL_ofType_(self, u, t): if u.isFileURL(): self = super(REDocument, self).initWithContentsOfURL_ofType_(u, t) self.useEmbeddedStyleSheet = YES return self self = self.init() if self: self.useEmbeddedStyleSheet = YES data = u.resourceDataUsingCache_(NO) if not data or not data.length(): print "failed to load URL." return None self.setContentFromString_(unicode(data.bytes(), 'utf-8')) return self def selectedFormat(self): return self.iv_selectedFormat selectedFormat = accessor(selectedFormat) def setSelectedFormat_(self, aFormat): reRenderRequired = self.iv_selectedFormat is not aFormat self.iv_selectedFormat = aFormat if reRenderRequired: self.windowController.renderContent_(None) setSelectedFormat_ = accessor(setSelectedFormat_) def selectedCSS(self): return self.iv_selectedCSS selectedCSS = accessor(selectedCSS) def setSelectedCSS_(self, anObject): reRenderRequired = self.iv_selectedCSS is not anObject self.iv_selectedCSS = anObject if reRenderRequired: self.windowController.renderContent_(None) setSelectedCSS_ = accessor(setSelectedCSS_) def windowNibName(self): return "REDocument" def pushContentToWindow(self): if self.windowController: self.windowController.setTextStorage_(self._content) def makeWindowControllers(self): windowNibName = self.windowNibName() self.windowController = REWindowController.alloc().initWithWindowNibName_(windowNibName) self.addWindowController_(self.windowController) def content(self): return self._content def setContentFromString_(self, aString): attributes = { NSFontAttributeName : NSFont.userFixedPitchFontOfSize_(12.0)} self._content = NSTextStorage.alloc().initWithString_attributes_(aString, attributes) self.pushContentToWindow() # ============================================== # read/write file # ============================================== def readFromUTF8_(self, fileName): f = file(fileName) text = unicode(f.read(), "utf-8") f.close() self._content = NSTextStorage.alloc().initWithString_(text) self.pushContentToWindow() def setFileName_(self, fileName): originalFileName = self.fileName() if (not originalFileName == fileName) and (fileName[-1] == '~'): self.revertDocumentToSaved_(None) return super(REDocument, self).setFileName_(fileName) def kqueue_receivedNotification_forFile_(self, kq, n, f): if not os.path.exists(self.fileName()): # this will happen on a save from emacs. The original file is moved to fileName~ and then the new file is written. # We catch the change in setFileName_() since we really want to follow the right file, not the backup file. return self.performSelectorOnMainThread_withObject_waitUntilDone_("revertDocumentToSaved:", None, NO) def monitorFile_(self, path): self.kqueue.removeAllPathsFromQueue() if path: self.kqueue.addPathToQueue_notifyingAbout_(path, ukkqueue.NOTE_DELETE | ukkqueue.NOTE_WRITE | ukkqueue.NOTE_EXTEND | ukkqueue.NOTE_RENAME) def readFromFile_ofType_(self, fileName, typeName): self.readFromUTF8_(fileName) self.monitorFile_(fileName) return True def revertAlertDidEnd_returnCode_contextInfo_(self, alert, code, context): if code == NSAlertFirstButtonReturn: self.readFromFile_ofType_(self.fileName(), self.fileType()) revertAlertDidEnd_returnCode_contextInfo_ = selector(revertAlertDidEnd_returnCode_contextInfo_, signature='v@:@i@') def close(self): self.kqueue.removeAllPathsFromQueue() super(REDocument, self).close() def revertDocumentToSaved_(self, sender): # unfortunately, NSDocument's revert doesn't actually revert to bytes on disk. if self.isDocumentEdited(): alert = NSAlert.alloc().init() alert.addButtonWithTitle_("OK") alert.addButtonWithTitle_("Cancel") alert.setMessageText_("The document has unsaved changes. The file may also have changed on disk. Do you still want to revert to saved?") alert.setInformativeText_("Unsaved changes will be lost.") alert.setAlertStyle_(NSWarningAlertStyle) alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.windowController.window(), self, "revertAlertDidEnd:returnCode:contextInfo:", 0) return self.readFromFile_ofType_(self.fileName(), self.fileType()) def writeToFile_ofType_(self, fileName, typeName): self.monitorFile_(None) f = file(fileName, "w") text = self._content.string() f.write(text.encode("utf-8")) f.close() self.monitorFile_(fileName) return True # ============================================== # generate content & export file # ============================================== def cssPath(self): return self.selectedCSS()['path'] def markdownScriptPath(self): return NSBundle.mainBundle().pathForResource_ofType_("Markdown", "pl") def generateContent(self): content = self.content() if not content or not len(content): return None if self.iv_selectedFormat == "ReST": settings = {} if self.useEmbeddedStyleSheet: settings['stylesheet'] = None settings['stylesheet_path'] = self.cssPath() settings['embed_stylesheet'] = 1 return publish_string(content.string(), reader_name="standalone", writer_name="html", settings_overrides=settings) elif self.iv_selectedFormat == "Markdown": cmd = [self.markdownScriptPath()] p = process.Popen(cmd, stdin=process.PIPE, stdout=process.PIPE, close_fds=True) (child_stdin, child_stdout) = (p.stdin, p.stdout) p.stdin.write(content.string()) p.stdin.close() return p.stdout.read() def exportHTML_(self, sender): html = self.generateContent() # save HTML file fn = self.fileName() if fn is None: NSBeep() return False path = splitext(fn)[0] + '.html' f = file(path, "w") f.write(html) f.close() return True def exportPDF_(self, sender): # run Docutils text = self.windowController.textView.string() try: tex = publish_string(text, writer_name="latex") except ImportError: NSBeep() print "No LaTeX installation found." return True path = os.path.splitext(self.path)[0] + '.tex' open(path, 'w').write(tex) folder = dirname(path) os.chdir(folder) # create empty style.tex file if not existing stylePath = join(folder, "style.tex") if not os.path.exists(stylePath): f = open(stylePath, "w") f.write('') f.close() # run LaTeX twice over the tweaked code format = "pdflatex --interaction nonstopmode %s;" args = basename(path) cmd = format % args res = os.popen(cmd*2).read() # cleanup base = splitext(basename(path))[0] for ext in ('log aux out'.split()): os.remove("%s.%s" % (base, ext)) return True def validateMenuItem_(self, menuItem): action = menuItem.action() if action == 'exportPDF:': return NSApp().delegate().pdfEnabled return super(REDocument, self).validateMenuItem_(menuItem)