aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/ventoo')
-rw-r--r--src/ventoo/AugEditTree.py119
-rw-r--r--src/ventoo/AugFileTree.py52
-rw-r--r--src/ventoo/ErrorDialog.py46
-rw-r--r--src/ventoo/RcUpdateWindow.py123
-rw-r--r--src/ventoo/VentooModule.py72
-rw-r--r--src/ventoo/__init__.py0
-rw-r--r--src/ventoo/augeas_utils.py240
-rw-r--r--src/ventoo/main.py434
-rw-r--r--src/ventoo/search_paths.py22
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']