aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristopher Harvey <chris@basementcode.com>2010-06-01 06:21:40 -0400
committerChristopher Harvey <chris@basementcode.com>2010-06-01 06:21:40 -0400
commita6686583ce7c72700acdf225a0b743fc4b2c42e0 (patch)
tree55b061db1b38b3e26e6dff0567b99a6049cd0b52
parentAdded .gitignore (diff)
downloadventoo-a6686583ce7c72700acdf225a0b743fc4b2c42e0.tar.gz
ventoo-a6686583ce7c72700acdf225a0b743fc4b2c42e0.tar.bz2
ventoo-a6686583ce7c72700acdf225a0b743fc4b2c42e0.zip
Moved project into official GSoC hosting from github.
-rw-r--r--modules/fstab/main.xml10
-rw-r--r--modules/group/main.xml9
-rw-r--r--modules/hosts/main.xml7
-rw-r--r--modules/pam/main.xml8
-rw-r--r--pix/folder-new.pngbin0 -> 1682 bytes
-rw-r--r--pix/folder.pngbin0 -> 4203 bytes
-rw-r--r--pix/system-search.pngbin0 -> 935 bytes
-rw-r--r--pix/ventoo-icon.pngbin0 -> 12731 bytes
-rw-r--r--pix/ventoo-icon2.pngbin0 -> 41336 bytes
-rw-r--r--pix/ventoo.pngbin0 -> 52626 bytes
-rw-r--r--pix/ventoo32x32.pngbin0 -> 4338 bytes
-rw-r--r--src/HEADER31
-rw-r--r--src/backend/VentooModule.py70
-rw-r--r--src/backend/augeas_utils.py239
-rw-r--r--src/frontend/AugEditTree.py120
-rw-r--r--src/frontend/AugFileTree.py52
-rw-r--r--src/frontend/ErrorDialog.py47
-rw-r--r--src/frontend/main.py350
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
new file mode 100644
index 0000000..3a79740
--- /dev/null
+++ b/pix/folder-new.png
Binary files differ
diff --git a/pix/folder.png b/pix/folder.png
new file mode 100644
index 0000000..1f1dd4c
--- /dev/null
+++ b/pix/folder.png
Binary files differ
diff --git a/pix/system-search.png b/pix/system-search.png
new file mode 100644
index 0000000..fd7f0b0
--- /dev/null
+++ b/pix/system-search.png
Binary files differ
diff --git a/pix/ventoo-icon.png b/pix/ventoo-icon.png
new file mode 100644
index 0000000..71607f3
--- /dev/null
+++ b/pix/ventoo-icon.png
Binary files differ
diff --git a/pix/ventoo-icon2.png b/pix/ventoo-icon2.png
new file mode 100644
index 0000000..eb6d34a
--- /dev/null
+++ b/pix/ventoo-icon2.png
Binary files differ
diff --git a/pix/ventoo.png b/pix/ventoo.png
new file mode 100644
index 0000000..3d65be6
--- /dev/null
+++ b/pix/ventoo.png
Binary files differ
diff --git a/pix/ventoo32x32.png b/pix/ventoo32x32.png
new file mode 100644
index 0000000..c67cc4b
--- /dev/null
+++ b/pix/ventoo32x32.png
Binary files differ
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()