diff options
author | Christopher Harvey <chris@basementcode.com> | 2010-06-01 06:21:40 -0400 |
---|---|---|
committer | Christopher Harvey <chris@basementcode.com> | 2010-06-01 06:21:40 -0400 |
commit | a6686583ce7c72700acdf225a0b743fc4b2c42e0 (patch) | |
tree | 55b061db1b38b3e26e6dff0567b99a6049cd0b52 | |
parent | Added .gitignore (diff) | |
download | ventoo-a6686583ce7c72700acdf225a0b743fc4b2c42e0.tar.gz ventoo-a6686583ce7c72700acdf225a0b743fc4b2c42e0.tar.bz2 ventoo-a6686583ce7c72700acdf225a0b743fc4b2c42e0.zip |
Moved project into official GSoC hosting from github.
-rw-r--r-- | modules/fstab/main.xml | 10 | ||||
-rw-r--r-- | modules/group/main.xml | 9 | ||||
-rw-r--r-- | modules/hosts/main.xml | 7 | ||||
-rw-r--r-- | modules/pam/main.xml | 8 | ||||
-rw-r--r-- | pix/folder-new.png | bin | 0 -> 1682 bytes | |||
-rw-r--r-- | pix/folder.png | bin | 0 -> 4203 bytes | |||
-rw-r--r-- | pix/system-search.png | bin | 0 -> 935 bytes | |||
-rw-r--r-- | pix/ventoo-icon.png | bin | 0 -> 12731 bytes | |||
-rw-r--r-- | pix/ventoo-icon2.png | bin | 0 -> 41336 bytes | |||
-rw-r--r-- | pix/ventoo.png | bin | 0 -> 52626 bytes | |||
-rw-r--r-- | pix/ventoo32x32.png | bin | 0 -> 4338 bytes | |||
-rw-r--r-- | src/HEADER | 31 | ||||
-rw-r--r-- | src/backend/VentooModule.py | 70 | ||||
-rw-r--r-- | src/backend/augeas_utils.py | 239 | ||||
-rw-r--r-- | src/frontend/AugEditTree.py | 120 | ||||
-rw-r--r-- | src/frontend/AugFileTree.py | 52 | ||||
-rw-r--r-- | src/frontend/ErrorDialog.py | 47 | ||||
-rw-r--r-- | src/frontend/main.py | 350 |
18 files changed, 943 insertions, 0 deletions
diff --git a/modules/fstab/main.xml b/modules/fstab/main.xml new file mode 100644 index 0000000..f8157ad --- /dev/null +++ b/modules/fstab/main.xml @@ -0,0 +1,10 @@ +<VentooModule> + <root mult="*"> + <spec mult="1"/> + <file mult="1"/> + <vfstype mult="1"/> + <opt mult="*"/> + <dump mult="1"/> + <passno mult="1"/> + </root> +</VentooModule>
\ No newline at end of file diff --git a/modules/group/main.xml b/modules/group/main.xml new file mode 100644 index 0000000..f35803b --- /dev/null +++ b/modules/group/main.xml @@ -0,0 +1,9 @@ +<VentooModule> + <root mult="1"> + <ventoo_dynamic mult="*"> + <password mult="1"/> + <gid mult="1"/> + <user mult="*"/> + </ventoo_dynamic> + </root> +</VentooModule>
\ No newline at end of file diff --git a/modules/hosts/main.xml b/modules/hosts/main.xml new file mode 100644 index 0000000..863d3f8 --- /dev/null +++ b/modules/hosts/main.xml @@ -0,0 +1,7 @@ +<VentooModule> + <root mult="*"> + <ipaddr mult="1"/> + <canonical mult="1"/> + <alias mult="*"/> + </root> +</VentooModule>
\ No newline at end of file diff --git a/modules/pam/main.xml b/modules/pam/main.xml new file mode 100644 index 0000000..4427771 --- /dev/null +++ b/modules/pam/main.xml @@ -0,0 +1,8 @@ +<VentooModule> + <root mult="*"> + <type mult="1"/> + <control mult="1"/> + <module mult="1"/> + <argument mult="*"/> + </root> +</VentooModule>
\ No newline at end of file diff --git a/pix/folder-new.png b/pix/folder-new.png Binary files differnew file mode 100644 index 0000000..3a79740 --- /dev/null +++ b/pix/folder-new.png diff --git a/pix/folder.png b/pix/folder.png Binary files differnew file mode 100644 index 0000000..1f1dd4c --- /dev/null +++ b/pix/folder.png diff --git a/pix/system-search.png b/pix/system-search.png Binary files differnew file mode 100644 index 0000000..fd7f0b0 --- /dev/null +++ b/pix/system-search.png diff --git a/pix/ventoo-icon.png b/pix/ventoo-icon.png Binary files differnew file mode 100644 index 0000000..71607f3 --- /dev/null +++ b/pix/ventoo-icon.png diff --git a/pix/ventoo-icon2.png b/pix/ventoo-icon2.png Binary files differnew file mode 100644 index 0000000..eb6d34a --- /dev/null +++ b/pix/ventoo-icon2.png diff --git a/pix/ventoo.png b/pix/ventoo.png Binary files differnew file mode 100644 index 0000000..3d65be6 --- /dev/null +++ b/pix/ventoo.png diff --git a/pix/ventoo32x32.png b/pix/ventoo32x32.png Binary files differnew file mode 100644 index 0000000..c67cc4b --- /dev/null +++ b/pix/ventoo32x32.png diff --git a/src/HEADER b/src/HEADER new file mode 100644 index 0000000..c599cb0 --- /dev/null +++ b/src/HEADER @@ -0,0 +1,31 @@ +Place this comment on the first line of each file. +Also add your name under as a copyright holder if you edit a file and +update the year under your name if nessasary. + +/* + + 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 <FISRTNAME> <LASTNAME> + +*/ + +For full instrctions: +http://www.gnu.org/licenses/gpl-howto.html + +also, never remove a year, just update a range, like +2007 becomes +2007-2008, if you edit the file created 2007 in 2008.
\ No newline at end of file diff --git a/src/backend/VentooModule.py b/src/backend/VentooModule.py new file mode 100644 index 0000000..77a81ea --- /dev/null +++ b/src/backend/VentooModule.py @@ -0,0 +1,70 @@ +""" + + 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 + +_ventoo_search_paths_ = ['../modules', '../../modules'] + +class VentooModule: + def __init__(self, moduleName): + #see if we can find the module files + found = False; + for p in _ventoo_search_paths_: + thisPath = osp.join(p,moduleName,"main.xml") + if osp.isfile(thisPath): + self.pathFound = thisPath + found = True + 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: + return elem[0].get("docurl") + except etree.XPathEvalError: + pass + return None + diff --git a/src/backend/augeas_utils.py b/src/backend/augeas_utils.py new file mode 100644 index 0000000..8ec0749 --- /dev/null +++ b/src/backend/augeas_utils.py @@ -0,0 +1,239 @@ +""" + + 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") + tmp = osp.relpath(sysPath, aug_root) + return osp.join('files/', 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' + #pdb.set_trace() + a.save() + for f in files: + #each file could exist, copy the ones that do. + if osp.isfile(f): + #pdb.set_trace() + 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/frontend/AugEditTree.py b/src/frontend/AugEditTree.py new file mode 100644 index 0000000..3400c91 --- /dev/null +++ b/src/frontend/AugEditTree.py @@ -0,0 +1,120 @@ +""" + + 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 +sys.path.append('../backend') +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/frontend/AugFileTree.py b/src/frontend/AugFileTree.py new file mode 100644 index 0000000..7e6c85f --- /dev/null +++ b/src/frontend/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/frontend/ErrorDialog.py b/src/frontend/ErrorDialog.py new file mode 100644 index 0000000..64ad119 --- /dev/null +++ b/src/frontend/ErrorDialog.py @@ -0,0 +1,47 @@ +""" + + 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 +sys.path.append('../backend') +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/frontend/main.py b/src/frontend/main.py new file mode 100644 index 0000000..71e2340 --- /dev/null +++ b/src/frontend/main.py @@ -0,0 +1,350 @@ +""" + + 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 +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 + +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") + augPath = osp.join('files', osp.relpath(self.currentConfigFilePath, aug_root), 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/<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)] + call = "meld " + diffFiles[0] + " " + diffFiles[1] + " &" + print call + if not osp.isfile(diffFiles[1]): + print "Could not find a diff file...were changes made?" + else: + os.system(call) + + 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]+)/?$') + 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 == '/': + print """ + +You're running this program on your root directory + This program sometimes has problems with the root directory. + If you're running as root it can't display diff. + if you're not running as root it can't save the system files + (because it doesn't have the permissions to do so. + + Displaying diffs as root will be fixed as soon as I figure out the + right way to do it. + + In the meantime, consider: + cp -r /etc /tmp + python thisProgram.py /tmp + (as non-root)""" + + #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() |