""" This file is part of the Ventoo program. This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this software. If not, see . Copyright 2010 Christopher Harvey """ import sys sys.path.append('../backend') import augeas import os.path as osp import pygtk pygtk.require('2.0') import gtk import augeas_utils import AugFileTree import VentooModule import AugEditTree import shutil import os import re import ErrorDialog import gtkmozembed import difflib from pygments import highlight from pygments.lexers import DiffLexer from pygments.formatters import HtmlFormatter sandboxDir = '/' class MainWindow(gtk.Window): def __init__(self, augeas): self.a = augeas #setup the gui gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) self.connect("delete_event", self.close) self.docBox = gtk.VPaned() self.rootBox = gtk.VBox() self.docWindow = gtkmozembed.MozEmbed() self.docBox.add2(self.docWindow) self.docWindow.load_url("about:blank") self.mainToolbar = gtk.Toolbar() self.diffButton = gtk.ToolButton(None, "Diff!") self.diffButton.connect("clicked", self.diffPressed, None) self.showErrorsButton = gtk.ToolButton(None, "Augeas Errors") self.showErrorsButton.connect("clicked", self.showErrPressed, None) self.mainToolbar.insert(self.diffButton, -1) self.mainToolbar.insert(self.showErrorsButton, -1) self.rootBox.pack_start(self.mainToolbar, False, False) self.mainPaned = gtk.HPaned() self.rootBox.pack_start(self.docBox) self.files_tv = AugFileTree.AugFileTree() self.edit_tv = AugEditTree.AugEditTree() self.edit_tv.connect("cursor-changed", self.nodeChanged, None) self.files_tv_scrolled_window = gtk.ScrolledWindow() self.files_tv_scrolled_window.add(self.files_tv) self.files_tv_scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) self.edit_tv_scrolled_window = gtk.ScrolledWindow() self.edit_tv_scrolled_window.add(self.edit_tv) self.edit_tv_scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) self.mainPaned.add1(self.files_tv_scrolled_window) self.mainPaned.add2(self.edit_tv_scrolled_window) self.docBox.add1(self.mainPaned) self.add(self.rootBox) self.files_tv.connect('cursor-changed', self.fileSelectionChanged, None) self.refreshAugeasFileList() self.currentModule = None self.set_default_size(800,600) self.edit_tv.connect("entry-edited", self.__rowEdited, None) self.edit_tv.connect("entry-toggled", self.__rowToggled, None) """ A row was enabled or disabled, update augeas tree, then refresh the view. """ def __rowToggled(self, editWidget, path, connectData): model = editWidget.get_model() thisIter = model.get_iter_from_string(path) enabled = model.get_value(thisIter, 0) aug_root = a.get("/augeas/root") if not aug_root == '/': augPath = osp.join('files', osp.relpath(self.currentConfigFilePath, aug_root), self.edit_tv.get_label_path(thisIter)) else: augPath = osp.join('files', augeas_utils.stripBothSlashes(self.currentConfigFilePath), self.edit_tv.get_label_path(thisIter)) if enabled: #this row was just added, update augeas tree. indexes = path.split(':') beforeIndex = int(indexes[len(indexes)-1])-1 if beforeIndex >= 0: beforePath = "" for i in indexes[0:len(indexes)-1]: beforePath = beforePath + str(i) + ":" beforePath = beforePath + str(beforeIndex) augBeforePath = self.edit_tv.get_label_path_str(beforePath) if not aug_root == '/': augBeforePath = osp.join('files', osp.relpath(self.currentConfigFilePath, aug_root), augBeforePath) else: augBeforePath = osp.join('files', augeas_utils.stripBothSlashes(self.currentConfigFilePath), augBeforePath) self.a.insert(augBeforePath, re.match('^.+/(.+?)(?:\\[[0-9]+\\])?$', augPath).group(1), False) #print "insert("+augBeforePath+" "+re.match('^.+/(.+?)(?:\\[[0-9]+\\])?$', augPath).group(1)+")" self.a.set(augPath, '') else: #this row was deleted, update augeas tree in the refresh print 'Would remove ' + augPath self.a.remove(augPath) self.refreshAugeasEditTree() """ This is called every time a selection in the AST edit window is changed. This function can be used to update documentation, code complete, finalize changes to the AST """ def nodeChanged(self, treeview, user_param1): p = self.edit_tv.getSelectedEntryPath() p = augeas_utils.removeNumbers(p) xmlPath = osp.join("/VentooModule/root", p) docPath = self.currentModule.getDocURLOf(xmlPath) if docPath == None: self.docWindow.load_url("about:blank") else: self.docWindow.load_url(docPath) """ Called when a row value (not label, not enabled/disabled) is changed. update augeas tree. No need to refresh here. """ def __rowEdited(self, editWidget, path, text, connectData): model = editWidget.get_model() thisIter = model.get_iter_from_string(path) enabled = model.get_value(thisIter, 0) aug_root = a.get("/augeas/root") #given a iter that was edited, and a current file, build the augeas path to the edited value. if not aug_root == "/": augPath = osp.join('files', osp.relpath(self.currentConfigFilePath, aug_root), self.edit_tv.get_label_path(thisIter)) else: augPath = osp.join('files', augeas_utils.stripBothSlashes(self.currentConfigFilePath), self.edit_tv.get_label_path(thisIter)) enteredValue = model.get_value(thisIter, 2) #get what the user entered. #print "Setting " + augPath + " = " + enteredValue self.a.set(augPath, enteredValue) def diffPressed(self, button, data=None): #show the diff for the current file. try: self.a.save() except IOError: pass #TODO: check augeas/files///error #to be sure the save worked. augeas_utils.makeDiffTree(self.a, augeas_utils.getDiffRoot()) diffFiles = [self.currentConfigFilePath, augeas_utils.getDiffLocation(self.a, self.currentConfigFilePath)] if not osp.isfile(diffFiles[1]): print "Could not find a diff file...were changes made?" else: origFile = open(diffFiles[0]) origList = file.readlines(origFile) origFile.close() newFile = open(diffFiles[1]) newList = file.readlines(newFile) newFile.close() #now we have origList and newList that is the text for the diff d = difflib.Differ() thediff = list(d.compare(origList, newList)) #TODO: append username, to avoid conflicts outFile = open("/tmp/ventooDiff.html", 'w') outFile.write("\n") theDiff = difflib.unified_diff(origList, newList) text = "" for l in theDiff: text += l highlight(text, DiffLexer(), HtmlFormatter(full=True, linenos=True, cssclass="source"), outFile) outFile.write("\n") outFile.close() self.docWindow.load_url("file:///tmp/ventooDiff.html") def showErrPressed(self, button, data=None): d = ErrorDialog.ErrorDialog(augeas_utils.getFileErrorList(self.a)) d.show_all() d.run() d.destroy() def close(widget, event, data=None): gtk.main_quit() return False def refreshAugeasFileList(self): #reload the file selection list from augeas internals. self.files_tv.clearFiles() fileList = augeas_utils.accumulateFiles(a) for f in fileList: self.files_tv.addPath(f) """ Given an augeas state and a ventoo module update the edit model to show that augeas state. The workhorse for this function is __buildEditModel() """ def refreshAugeasEditTree(self): model = self.edit_tv.get_model() model.clear() # aRoot = files/etc/foo for the root of the tree to build. # sketchy file manipulations, TODO: make this more clear. tmp = augeas_utils.stripTrailingSlash(self.files_tv.getSelectedConfigFilePath()) if sandboxDir != '/': tmp = osp.relpath(self.files_tv.getSelectedConfigFilePath(), sandboxDir) aRoot = osp.join('files', augeas_utils.stripBothSlashes(tmp)) # a path into the tree model, coresponds to the aRoot mRoot = model.get_iter_root() #path into xml description of the tree xRoot = '/VentooModule/root' self.__buildEditModel(model, aRoot, mRoot, xRoot) """ this is the workhorse behind refreshAugeasEditTree() This is the core function for displaying the editing widget tree. It can be considered the core of the whole program actually. This code has to be rock solid. """ def __buildEditModel(self, model, augeasFileRoot, modelPathIter, xmlRoot): xElemRoot = self.currentModule.getChildrenOf(osp.join(xmlRoot, '*')) xChildren = list(self.currentModule.getChildrenOf(xmlRoot)) thisMult = self.currentModule.getMultOf(xmlRoot) if augeas_utils.isDupLevel(self.a, augeasFileRoot): #this level is just /1 /2 /3, etc... #for each match # created = model.append(modelPathIter, [not addedExtra, str(i), '------']) # if enabled # self.__buildEditModel(model, osp.join(augeasFileRoot, str(i)), created, xmlRoot) matches = self.a.match(osp.join(augeasFileRoot, '*')) have = 0 maxIndex = 0 #matches / and stores as group 1 indexProg = re.compile('^.+/([0-9]+)/?$') for match in matches: #add all existing entries indexResult = indexProg.match(match) if indexResult != None: #sometimes there are entries on these levels we don't care about. have += 1 thisIndex = int(indexResult.group(1)) maxIndex = max(maxIndex, thisIndex) created = model.append(modelPathIter, [True, indexResult.group(1), '-------']) self.__buildEditModel(model, match, created, xmlRoot) #add the missing entries numNeeded = augeas_utils.matchDiff(thisMult, have) #add the required ones. for i in range(numNeeded): created = model.append(modelPathIter, [True, osp.join(augeasFileRoot, str(have+i+1)), '-------']) self.__buildEditModel(model, match, created, xmlRoot) have += 1 needOption = not augeas_utils.matchExact(thisMult, have) if needOption: created = model.append(modelPathIter, [False, str(have+1), '-------']) else: listedNodes = [] #a list of nodes that we already found and know about. for child in xChildren: #build get a list of either [child.tag] or [child.tag[1], child.tag[n]] childMult = self.currentModule.getMultOf(osp.join(xmlRoot, child.tag)) matches = self.a.match(osp.join(augeasFileRoot, child.tag)) matches.extend(self.a.match(osp.join(augeasFileRoot, child.tag)+'[*]')) listedNodes.extend(matches) #add leaves if we're missing some required ones (in augeas itself) have = len(matches) numNeeded = augeas_utils.matchDiff(childMult, have) for i in range(have+1, have+numNeeded+1): p = osp.join(augeasFileRoot, child.tag) if have+numNeeded > 1: p = p + '[' + str(i) + ']' print 'added ' + p + ' to augeas' self.a.set(p, '') #update the matches, since we have added stuff to augeas, based on previous matches matches = self.a.match(osp.join(augeasFileRoot, child.tag)) matches.extend(self.a.match(osp.join(augeasFileRoot, child.tag)+'[*]')) for match in matches: userData = self.a.get(match) #add all existing data if userData == None: userData = '' created = model.append(modelPathIter, [True, osp.split(match)[1], userData]) self.__buildEditModel(model, match, created, osp.join(xmlRoot, child.tag)) #maybe we need to add more of child to the tree, and maybe even an option for the user. have = len(matches) needed = not augeas_utils.matchExact(childMult, have) numNeeded = augeas_utils.matchDiff(childMult, have) if needed: i = 0 while True: foo = True if numNeeded == 0: foo = False newLabel = child.tag if True: newLabel = newLabel + '['+str(have+i+1)+']' created = model.append(modelPathIter, [foo, newLabel, '']) if foo: self.__buildEditModel(model, 'no_data', created, osp.join(xmlRoot, child.tag)) i += 1 if augeas_utils.matchExact(childMult, have+i): break if not foo: break #now search for and add nodes that haven't been added yet, and may not be in the VentooModule specifically. allInAugeas = self.a.match(osp.join(augeasFileRoot, '*')) for a in allInAugeas: if not a in listedNodes: #found that 'a' is not in listedNodes, but is in augeasTree, add it, if it is not supposed to be ignored. if not osp.split(a)[1].startswith('#'): #always ignore comments userData = self.a.get(a) created = model.append(modelPathIter, [True, osp.split(a)[1], userData]) self.__buildEditModel(model, a, created, osp.join(xmlRoot, 'ventoo_dynamic')) """ Called when the user picks a new file to view. """ def fileSelectionChanged(self, tv, data=None): #uer picked a new file to edit. self.currentConfigFilePath = self.files_tv.getSelectedConfigFilePath() #update the display...and get new module info. #thse path manipulations are sketchy, should make this code clearer. tmp = self.currentConfigFilePath if sandboxDir != '/': tmp = osp.relpath(self.currentConfigFilePath, sandboxDir) self.currentModule = VentooModule.VentooModule(augeas_utils.getVentooModuleNameFromSysPath(a, tmp)) self.refreshAugeasEditTree() if __name__ == '__main__': if len(sys.argv) > 1: sandboxDir = sys.argv[1] if not osp.isdir(sandboxDir): print sandboxDir + " is not a directory." sys.exit(0) print 'Starting augeas...' #None could be a 'loadpath' a = augeas.Augeas(sandboxDir, None, augeas.Augeas.SAVE_NEWFILE) print 'Creating window...' if sandboxDir == '/': pass #Note, it IS possible to create mutiple windows and augeas #instances to edit multiple "roots" at the same time. window = MainWindow(a) window.show_all() #clear the diff storage place... shutil.rmtree(augeas_utils.getDiffRoot(), True) os.makedirs(augeas_utils.getDiffRoot()) gtk.main()