diff options
Diffstat (limited to 'src/ventoo')
-rw-r--r-- | src/ventoo/AugEditTree.py | 119 | ||||
-rw-r--r-- | src/ventoo/AugFileTree.py | 52 | ||||
-rw-r--r-- | src/ventoo/ErrorDialog.py | 46 | ||||
-rw-r--r-- | src/ventoo/RcUpdateWindow.py | 123 | ||||
-rw-r--r-- | src/ventoo/VentooModule.py | 72 | ||||
-rw-r--r-- | src/ventoo/__init__.py | 0 | ||||
-rw-r--r-- | src/ventoo/augeas_utils.py | 240 | ||||
-rw-r--r-- | src/ventoo/main.py | 434 | ||||
-rw-r--r-- | src/ventoo/search_paths.py | 22 |
9 files changed, 1108 insertions, 0 deletions
diff --git a/src/ventoo/AugEditTree.py b/src/ventoo/AugEditTree.py new file mode 100644 index 0000000..5ad62cc --- /dev/null +++ b/src/ventoo/AugEditTree.py @@ -0,0 +1,119 @@ +""" + + 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 <http://www.gnu.org/licenses/>. + + Copyright 2010 Christopher Harvey + +""" + +import sys +import pygtk +pygtk.require('2.0') +import gtk +import gobject +import os.path as osp +import augeas_utils + +class AugEditTree(gtk.TreeView): + def __init__(self): + #make storage enable/disable label user entry + self.tv_store = gtk.TreeStore('gboolean', str, str) + #make widget + gtk.TreeView.__init__(self, self.tv_store) + #make renderers + self.buttonRenderer = gtk.CellRendererToggle() + self.labelRenderer = gtk.CellRendererText() + self.entryRenderer = gtk.CellRendererText() + self.buttonRenderer.set_property("activatable", True) + self.entryRenderer.set_property("editable", True) + self.entryRenderer.connect("edited", self.entry_edited) + self.buttonRenderer.connect("toggled", self.entry_toggled) + + #make columns + self.columnButton = gtk.TreeViewColumn('Enabled') + self.columnButton.pack_start(self.buttonRenderer, False) + + self.columnLabel = gtk.TreeViewColumn('Label') + self.columnLabel.pack_start(self.labelRenderer, False) + + self.columnEntry = gtk.TreeViewColumn('Data') + self.columnEntry.pack_start(self.entryRenderer, True) + + self.columnButton.add_attribute(self.buttonRenderer, 'active', 0) + self.columnLabel.add_attribute(self.labelRenderer, 'text', 1) + self.columnEntry.add_attribute(self.entryRenderer, 'text', 2) + + #add columns + self.append_column(self.columnButton) + self.append_column(self.columnLabel) + self.append_column(self.columnEntry) + + gobject.signal_new("entry-edited", + AugEditTree, + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING, + gobject.TYPE_STRING)) + + gobject.signal_new("entry-toggled", + AugEditTree, + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING,)) + + + def entry_edited(self, cell, path, text): + #column = int(path) + self.tv_store[path][2] = text + self.emit("entry-edited", path, text) + + def entry_toggled(self, cell, path): + #column = int(path) + self.tv_store[path][0] = not self.tv_store[path][0] + self.emit("entry-toggled", path) + + """ + Returns a path in the form of a/b/c to the currently selected node. + """ + def getSelectedEntryPath(self): + currentSelection = self.get_selection() + if currentSelection.count_selected_rows()!=1: + raise RuntimeException("User selected more that one row from the file list...should never be possible.") + selectedSystemPathTuple = currentSelection.get_selected() + selectedIter = selectedSystemPathTuple[1] + return self.get_label_path(selectedIter) + + + """ + Gets a path of the form a/b/c where c is the + element pointed to by the iterator iter. + """ + def get_label_path(self, inputIter): + ret = '' + i = inputIter + while True: + ret = augeas_utils.stripBothSlashes(osp.join(self.tv_store.get_value(i, 1), ret)) + i = self.tv_store.iter_parent(i) + if i == None: + break + return ret + + """ + Same as get_label_path, except converts a string path to an + iterator for you. + """ + def get_label_path_str(self, path): + return self.get_label_path(self.tv_store.get_iter(path)) diff --git a/src/ventoo/AugFileTree.py b/src/ventoo/AugFileTree.py new file mode 100644 index 0000000..7e6c85f --- /dev/null +++ b/src/ventoo/AugFileTree.py @@ -0,0 +1,52 @@ +""" + + 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 <http://www.gnu.org/licenses/>. + + Copyright 2010 Christopher Harvey + +""" + +import os.path as osp +import pygtk +pygtk.require('2.0') +import gtk + +class AugFileTree(gtk.TreeView): + def __init__(self): + self.tv_store = gtk.TreeStore(str) + gtk.TreeView.__init__(self, self.tv_store) + self.column = gtk.TreeViewColumn('Parsed files') + self.append_column(self.column) + self.cell = gtk.CellRendererText() + # add the cell to the tvcolumn and allow it to expand + self.column.pack_start(self.cell, True) + # set the cell "text" attribute to column 0 - retrieve text + # from that column in treestore + self.column.add_attribute(self.cell, 'text', 0) + + def addPath(self, p): + self.tv_store.append(None, [p]) + + def clearFiles(self): + self.tv_store.clear() + + def getSelectedConfigFilePath(self): + currentSelection = self.get_selection() + if currentSelection.count_selected_rows()!=1: + raise RuntimeException("User selected more that one row from the file list...should never be possible.") + selectedSystemPathTuple = currentSelection.get_selected() + selectedIter = selectedSystemPathTuple[1] + return self.tv_store.get_value(selectedIter, 0) diff --git a/src/ventoo/ErrorDialog.py b/src/ventoo/ErrorDialog.py new file mode 100644 index 0000000..bd22c1e --- /dev/null +++ b/src/ventoo/ErrorDialog.py @@ -0,0 +1,46 @@ +""" + + 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 <http://www.gnu.org/licenses/>. + + Copyright 2010 Christopher Harvey + +""" + +import sys +import os.path as osp +import pygtk +pygtk.require('2.0') +import gtk +import augeas_utils + +class ErrorDialog(gtk.Dialog): + def __init__(self, errorDict): + gtk.Dialog.__init__(self, "Error dialog", None, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) + self.set_default_size(400,300) + errorScroll = gtk.ScrolledWindow() + errorBox = gtk.TextView() + errorBox.set_editable(False) + errorScroll.add(errorBox) + errorScroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.vbox.pack_start(errorScroll) + errorBuf = errorBox.get_buffer() + for k, v in errorDict.iteritems(): + errorBuf.insert_at_cursor(k + " -> " + v + "\n") + + + diff --git a/src/ventoo/RcUpdateWindow.py b/src/ventoo/RcUpdateWindow.py new file mode 100644 index 0000000..5642c03 --- /dev/null +++ b/src/ventoo/RcUpdateWindow.py @@ -0,0 +1,123 @@ +""" + + 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 <http://www.gnu.org/licenses/>. + + Copyright 2010 Christopher Harvey + + +""" + +import sys +import pygtk +pygtk.require('2.0') +import gtk +import gobject +import os.path as osp +import augeas_utils +import subprocess +import getpass +import os +import re +import pdb + +class RcUpdateWindow(gtk.Window): + def __init__(self): + gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) + self.set_default_size(300, 500) + self.connect("delete_event", self.close) + self.runlevels = ["boot", "default", "nonetwork"] + # name boot default nonetwork + self.tv_store = gtk.ListStore(str, 'gboolean', 'gboolean', 'gboolean') + self.tv_store.set_sort_column_id(0, gtk.SORT_ASCENDING) + self.tv_view = gtk.TreeView(self.tv_store) + + #renderers and columns + nameRenderer = gtk.CellRendererText() + nameColumn = gtk.TreeViewColumn("Script Name") + nameColumn.pack_start(nameRenderer, False) + nameColumn.add_attribute(nameRenderer, 'text', 0) + self.tv_view.append_column(nameColumn) + i = 1 + for level in self.runlevels: + renderer = gtk.CellRendererToggle() + renderer.set_property("activatable", True) + renderer.connect("toggled", self.entry_toggled, i) + column = gtk.TreeViewColumn(level) + column.pack_start(renderer, False) + column.add_attribute(renderer, 'active', i) + self.tv_view.append_column(column) + i += 1 + + scrollWindow = gtk.ScrolledWindow() + scrollWindow.add(self.tv_view) + scrollWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) + self.add(scrollWindow) + + #fill model with data + reportedData = [] + username = getpass.getuser() + if username == 'root': + process = subprocess.Popen("rc-update -s", stdout=subprocess.PIPE, shell=True) + os.waitpid(process.pid, 0) + output = process.stdout.read().strip() + output = output.split('\n') + for line in output: + datas = line.split(' ') + datas = filter(lambda a: a != '', datas) #remove all '' + datas = filter(lambda a: a != '|', datas) #remove the '|' + scriptName = datas[0] + reportedData.append(scriptName) + datas = datas[1:] + rowData = [scriptName] + for level in self.runlevels: + if level in datas: + rowData.append(True) + else: + rowData.append(False) + self.tv_store.append(rowData) + + #fill in the rest of the data that rc-update -s hasn't reported. + allNames = os.listdir("/etc/init.d") + for name in allNames: + if not name in reportedData: #this name wasn't reported by rc-update -s + rowData = [False]*len(self.runlevels) + rowData.insert(0, name) + self.tv_store.append(rowData) + #TODO: sort the view. + else: + pass #say something about having to be a root user to rc-update + + def entry_toggled(self, cell, path, column): + add = not self.tv_store[path][column] + scriptName = self.tv_store[path][0] + call = ["rc-update"] + if add: + call.append("add") + else: + call.append("del") + call.append(scriptName) + level = self.runlevels[column-1] + call.append(level) + retcode = subprocess.call(call) + if retcode == 0: + self.tv_store[path][column] = not self.tv_store[path][column] + else: + print "Call to rc-update failed." #TODO: print nice error. + #self.emit("entry-toggled", path) + pass + + def close(self, widget, event, data=None): + pass diff --git a/src/ventoo/VentooModule.py b/src/ventoo/VentooModule.py new file mode 100644 index 0000000..35c79f9 --- /dev/null +++ b/src/ventoo/VentooModule.py @@ -0,0 +1,72 @@ +""" + + 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 <http://www.gnu.org/licenses/>. + + Copyright 2010 Christopher Harvey + +""" + +import os.path as osp +from lxml import etree +import augeas_utils +import search_paths + +class VentooModule: + def __init__(self, moduleName): + #see if we can find the module files + found = False; + for p in search_paths.modules: + thisPath = osp.join(p,moduleName,"main.xml") + if osp.isfile(thisPath): + self.pathFound = thisPath + found = True + self.docRoot = osp.join(p, moduleName) + break + if not found: + raise RuntimeError('Could not find '+moduleName+' Module') + self.xmlTree = etree.parse(self.pathFound) + #validate the module main.xml file. + #make sure it starts with <VentooModule> + self.xmlRoot = self.xmlTree.getroot() + if self.xmlRoot.tag != "VentooModule": + raise RuntimeError('Ventoo modules need to start with <VentooModule>') + + + def getChildrenOf(self, xPath): + children = self.xmlTree.xpath(osp.join(xPath, '*')) + ret = [] + for i in range(len(children)): + if not children[i].tag.startswith('ventoo_'): + ret.extend([children[i]]) + return ret + + def getMultOf(self, xPath): + elem = self.xmlTree.xpath(osp.join(xPath)) + if len(elem) >= 1: + return elem[0].get("mult") + else: + return '0' + + def getDocURLOf(self, xPath): + try: + elem = self.xmlTree.xpath(osp.join(xPath)) + if len(elem) >= 1 and not elem[0].get("docurl") == None: + #pdb.set_trace() + return "file:///"+osp.abspath(osp.join(self.docRoot, augeas_utils.stripBothSlashes(elem[0].get("docurl")))) + except etree.XPathEvalError: + pass + return None + diff --git a/src/ventoo/__init__.py b/src/ventoo/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/ventoo/__init__.py diff --git a/src/ventoo/augeas_utils.py b/src/ventoo/augeas_utils.py new file mode 100644 index 0000000..6db6551 --- /dev/null +++ b/src/ventoo/augeas_utils.py @@ -0,0 +1,240 @@ +""" + + 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 <http://www.gnu.org/licenses/>. + + Copyright 2010 Christopher Harvey + +""" + +import augeas +import os.path as osp +import getpass +import shutil +import os +import pdb + +""" + Fills the fileSet parameter with the files that augeas loaded. + The returned files are system paths +""" +def accumulateFiles(a, node="/files", fileSet=[]): + aug_root = a.get("/augeas/root") + thisChildren = a.match(osp.join(node, '*')) + for child in thisChildren: + sysPath = osp.join(aug_root, osp.relpath(child, '/files')) + if osp.isfile(sysPath): + fileSet.append(sysPath) + elif osp.isdir(sysPath): + accumulateFiles(a, child, fileSet) + return fileSet + + +""" + Use an augeas file path to get a ventoo module name. +""" +def getVentooModuleNameFromAugPath(a, augPath): + aug_root = a.get("/augeas/root") + sysPath = osp.join(aug_root, osp.relpath(augPath, '/files')) + return str.split(osp.split(sysPath)[1], ".")[0] + +""" + Use a full system path to get a ventoo module name. +""" +def getVentooModuleNameFromSysPath(a, sysPath): + #remove the first character '/' if sysPath is absolute or join wont work. + if sysPath[0] == '/': + augQuery = osp.join('augeas/files', sysPath[1:], 'lens/info') + else: + augQuery = osp.join('augeas/files', sysPath, 'lens/info') + lensFile = a.get(augQuery) + return str.split(osp.split(lensFile)[1], ".")[0] + + +""" + Get an augeas path to a file from a system path +""" +def getAugeasPathFromSystemPath(a, sysPath): + aug_root = a.get("/augeas/root") + if aug_root != '/': + tmp = osp.relpath(sysPath, aug_root) + else: + tmp = sysPath + return osp.join('/files/', stripLeadingSlash(tmp)) + +""" + Return an int that says how much to add to 'have' so that it matches mult. + Mult can be a number, or ?, +, * +""" +def matchDiff(mult, have): + if mult == '*': + return 0 + elif mult == '+': + if have > 0: + return 0 + else: + return 1 + elif mult == '?': + return 0 + else: + return max(0, int(mult) - have) + +""" + True if adding more to 'have' will make the match invalid. +""" +def matchExact(mult, have): + if mult == '*': + return False + elif mult == '+': + return False + elif mult == '?': + if have == 0: + return False + elif have == 1: + return True + else: + raise ValueError("passed ? and "+str(have)+" to matchExact") + elif int(mult) == have: + return True + elif int(mult) < have: + raise ValueError("passed "+mult+" and "+str(have)+" to matchExact") + return False + +""" + True if the children in augPath are + augPath/1 + augPath/2 + etc... +""" +def isDupLevel(a, augPath): + q = osp.join(augPath, '*') + matches = a.match(q) + for match in matches: + try: + int(osp.split(match)[1]) + return True + except ValueError: + pass + return False + +""" + Turn /foo/bar/ into /foo/bar +""" +def stripTrailingSlash(a): + if a.endswith('/'): + ret = a[0:len(a)-1] + else: + ret = a + return ret + +""" + Turn /foo/bar/ into foo/bar/ +""" +def stripLeadingSlash(a): + if a.startswith('/'): + ret = a[1:] + else: + ret = a + return ret + +def stripBothSlashes(a): + return stripTrailingSlash(stripLeadingSlash(a)) + +""" + Get the path to the directory where the diff tree is to be stored. +""" +def getDiffRoot(): + return osp.join('/tmp', getpass.getuser(), 'augeas') + + +""" + Called by makeDiffTree, this actually checks files and performs the copies. + makeDiffTree only walks the directories and sets up the base paths. +""" +def __makeCopies(bases, currentDir, files): + #bases[0] = copyTargetBase + #bases[1] = aug_root + if osp.commonprefix([bases[0], currentDir]) == bases[0]: + return #ignore anything inside where we are copying to. + for f in files: + if f.endswith(".augnew"): + #print 'would copy ' + srcFile + ' to ' + targetDir + srcFile = osp.join(currentDir, f) + targetDir = osp.join(bases[0], osp.relpath(currentDir, bases[1])) + targetFile = osp.join(targetDir, f) + try: + os.makedirs(targetDir) #make sure target dir exists + except: + pass #don't care if it already exists. + shutil.copy(srcFile, targetFile) + +""" + Make a tree in treeRoot that contains a replica of the edited + filesystem except with .augnew files for future comparisions. +""" +def makeDiffTree(a, treeRoot): + saveMethod = a.get('augeas/save') + userName = getpass.getuser() + #if userName == 'root': + # raise ValueError("this function isn't safe to be run as root.") + if saveMethod != 'newfile': + raise ValueError('the save method for augeas is not "newfile"') + if not osp.isdir(treeRoot): + raise ValueError(treeRoot + ' is not a directory') + files = accumulateFiles(a) + for i in range(len(files)): #append .augnew to each file. + files[i] += '.augnew' + a.save() + for f in files: + #each file could exist, copy the ones that do. + if osp.isfile(f): + try: + os.makedirs(osp.join("/tmp", userName, "augeas", stripLeadingSlash(osp.split(f)[0]))) + except: + pass #don't care if it exists. + shutil.move(f, osp.join("/tmp", userName, "augeas", stripLeadingSlash(f))) +""" + Turns a system path like /etc/hosts into a the place where its + diff (edited) version would be saved. Use this function to compare + two files. +""" +def getDiffLocation(a, sysPath): + aug_root = a.get("/augeas/root") + return osp.join(getDiffRoot(), stripBothSlashes(sysPath)) + '.augnew' + + +def getFileErrorList(a, node = "augeas/files", errorList = {}): + aug_root = a.get("/augeas/files/") + thisChildren = a.match(osp.join(node, '*')) + for child in thisChildren: + thisError = a.get(osp.join(child, "error")) + if not thisError == None: + errorList[osp.relpath(child, "/augeas/files")] = thisError + else: + getFileErrorList(a, child, errorList) + return errorList + +""" + Removes numbers form paths. a/1/foo/bar/5/6/blah -> a/foo/bar/blah +""" +def removeNumbers(path): + out = "" + for node in path.split("/"): + try: + tmp = int(node) + except ValueError: + out = osp.join(out, node) + return out + diff --git a/src/ventoo/main.py b/src/ventoo/main.py new file mode 100644 index 0000000..84fe695 --- /dev/null +++ b/src/ventoo/main.py @@ -0,0 +1,434 @@ +""" + + 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 <http://www.gnu.org/licenses/>. + + Copyright 2010 Christopher Harvey + +""" + +import sys +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 +import RcUpdateWindow + +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.showRCUpdateButton = gtk.ToolButton(None, "rc-update") + self.showRCUpdateButton.connect("clicked", self.showRCUpdate, None) + self.showErrorsButton.connect("clicked", self.showErrPressed, None) + self.mainToolbar.insert(self.diffButton, -1) + self.mainToolbar.insert(self.showErrorsButton, -1) + self.mainToolbar.insert(self.showRCUpdateButton, -1) + self.rootBox.pack_start(self.mainToolbar, False, False) + self.mainPaned = gtk.HPaned() + self.rootBox.pack_start(self.docBox) + self.applyDiffButton = gtk.Button("Apply diff") + self.applyDiffButton.connect("clicked", self.applyDiffPressed, None) + self.rootBox.pack_start(self.applyDiffButton, False, False) + self.hideApplyDiffButton() + 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() + docPath = None + #if this is a comment node display all surround docs/comments + if osp.split(p)[1].startswith("#comment["): + toDisplay = self.getDocsStartingAt(osp.join(augeas_utils.getAugeasPathFromSystemPath(self.a, self.currentConfigFilePath), p), "<br>") + docPath = "/tmp/commentsDoc.html" + outFile = open("/tmp/commentsDoc.html", 'w') + outFile.write("<html>\n") + outFile.write(toDisplay) + outFile.write("</html>\n") + outFile.close() + else: + #this is a normal node, normal display + 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/<file path>/<name>/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("<html>\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("</html>\n") + outFile.close() + self.docWindow.load_url("file:///tmp/ventooDiff.html") + #TODO: check to make sure diff is correctly displayed (html file found) + self.showApplyDiffButton() + + def applyDiffPressed(self, button, data=None): + diffFiles = [self.currentConfigFilePath, augeas_utils.getDiffLocation(self.a, self.currentConfigFilePath)] + #merge diffFiles[0] <- diffFiles[1] + shutil.copyfile(diffFiles[1], diffFiles[0]) + + def showRCUpdate(self, button, data=None): + win = RcUpdateWindow.RcUpdateWindow() + win.show_all() + + 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 <anything>/<number> and stores <number> as group 1 + indexProg = re.compile('^.+/([0-9]+)/?$') + commentProg = re.compile('"^#comment\[(.*)]$"') + lastLineWasDoc = False + for match in matches: #add all existing entries + indexResult = indexProg.match(match) + commentResult = commentProg.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) + lastLineWasDoc = False + elif commentProg != None: #got a comment + #only display the first line, the rest can be displayed in the doc frame when requested. + if not lastLineWasDoc: + docText = self.a.get(match) + created = model.append(modelPathIter, [True, osp.split(match)[1], docText]) + lastLineWasDoc = True + + #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)+'[*]')) + matches = list(set(matches)) #remove dups from matches + 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)+'[*]')) + matches = list(set(matches)) #remove dups from matches + 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, '*')) + lastLineWasDoc = False + 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 (delt with later) + 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')) + lastLineWasDoc = False + else: + #Add docs inline here + if not lastLineWasDoc: + docText = self.a.get(a) + created = model.append(modelPathIter, [True, osp.split(a)[1], docText]) + lastLineWasDoc = True + + """ + Get doc text starting at the augeas path p, until another non-comment is found + nl stands for new line and is a string to be added after each comment node. + """ + def getDocsStartingAt(self, p, nl = "\n"): + docStr = "" + commentProg = re.compile('^.*#comment\[(.*)\]$') + thisNodes = self.a.match(osp.join(osp.split(p)[0], "*")) + try: + idx = thisNodes.index(p) + while commentProg.match(thisNodes[idx])!=None: + docStr = docStr + nl + self.a.get(thisNodes[idx]) + idx += 1 + except IndexError: + pass + return docStr + + """ + Called when the user picks a new file to view. + """ + def fileSelectionChanged(self, tv, data=None): + #user picked a new file to edit. + self.hideApplyDiffButton() + 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() + + def hideApplyDiffButton(self): + self.applyDiffButton.hide() + + def showApplyDiffButton(self): + self.applyDiffButton.show() + +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() + window.hideApplyDiffButton() #TODO: overload show_all to preserve apply button state. + + #clear the diff storage place... + shutil.rmtree(augeas_utils.getDiffRoot(), True) + os.makedirs(augeas_utils.getDiffRoot()) + gtk.main() diff --git a/src/ventoo/search_paths.py b/src/ventoo/search_paths.py new file mode 100644 index 0000000..5c0c24d --- /dev/null +++ b/src/ventoo/search_paths.py @@ -0,0 +1,22 @@ +""" + + 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 <http://www.gnu.org/licenses/>. + + Copyright 2010 Christopher Harvey + +""" + +modules = ['../modules', '../../modules'] |