From 30c58a91fee72e2d5fb6b429a97343a12164e93e Mon Sep 17 00:00:00 2001 From: necoro <> Date: Tue, 7 Nov 2006 20:59:15 +0000 Subject: new config-parser and a new layout for the config file --- doc/Changelog | 3 + etc/geneticone.cfg | 43 ++++++- geneticone/config_parser.py | 274 ++++++++++++++++++++++++++++++++++++++++++ geneticone/gui/gtk/windows.py | 4 +- geneticone/gui/gui_helper.py | 45 +++---- 5 files changed, 344 insertions(+), 25 deletions(-) create mode 100644 geneticone/config_parser.py diff --git a/doc/Changelog b/doc/Changelog index 19403d5..87595b3 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +0.5.1: +- new config parser and new config-layout + 0.5.0: - rewritten GTK-Frontend using Glade - made the wrapper more powerful diff --git a/etc/geneticone.cfg b/etc/geneticone.cfg index 947cf18..9afc647 100644 --- a/etc/geneticone.cfg +++ b/etc/geneticone.cfg @@ -1,10 +1,49 @@ +# +# Configuration file for Genetic/One +# ================================== +# +# Allowed boolean values (case insensitive): +# on <-> off +# yes <-> no +# true <-> false +# 1 <-> 0 +# +# Comments are single-line only and are started with a '#' or a ';'. +# +# Values can be assigned to options via a '=' or ':' - so "option = value" is the same as "option : value" +# + +# +# Main section - for general, frontend independent options +# [Main] + +; controls debug output - boolean value +debug = True + +; control the same-named "emerge --update" options - boolean values newuse = False -keywordperversion = True deep = False + +; control the name of the particular file if package.* is a directory - string values +; allowed placeholders: +; - $(pkg) : package-name +; - $(cat) : category-name +; - $(cat-1)/$(cat-2) : first/second part of the category name usefile = geneticone maskfile = geneticone keywordfile = geneticone + +; control whether the option is inserted into package.* with a specific version or not +keywordperversion = True maskperversion = True useperversion = True -debug = True + +# +# GTK-Section for options of the GTK-Frontend +# +[Gtk] + +; empty + +# vim:ts=4:sw=4:ft=cfg diff --git a/geneticone/config_parser.py b/geneticone/config_parser.py new file mode 100644 index 0000000..99e5b6c --- /dev/null +++ b/geneticone/config_parser.py @@ -0,0 +1,274 @@ +# -*- coding: utf-8 -*- +# +# File: geneticone/config_parser.py +# This file is part of the Genetic/One-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# This is free software. You may redistribute copies of it under the terms of +# the GNU General Public License version 2. +# There is NO WARRANTY, to the extent permitted by law. +# +# Written by René 'Necoro' Neumann + +from helper import debug + +import re +import types + +DELIMITER = ["=", ":"] +COMMENT = [";","#"] + +# precompiled expressions +TRUE = re.compile("((true)|(1)|(on)|(wahr)|(ja)|(yes))", re.I) +FALSE = re.compile("((false)|(0)|(off)|(falsch)|(nein)|(no))", re.I) +SECTION = re.compile("\s*\[(\w+)\]\s*") +EXPRESSION = re.compile(r"\s*(\w+)\s*[:=]\s*(.*)\s*") + +class Value (object): + """Class defining a value of a key.""" + + def __init__ (self, value, line, bool = None): + """Constructor. + + @param value: the value + @type value: string + @param line: the line in the config file + @type line: int + @param bool: boolean meaning of the value + @type bool: boolean""" + + self.__value = value + self.line = line + self.boolean = bool + + self.changed = False # true if we changed it + self.old = value # keep the original one ... so if we change it back to this one, we do not have to write + + def set (self, value): + """Sets the value to a new one. + + @param value: new value + @type value: string""" + + self.__value = value + + if value != self.old: + self.changed = True + else: + self.changed = False + + def get (self): + """Returns the actual value. + + @returns: the actual value + @rtype: string""" + + return self.__value + + def is_bool (self): + """Returns whether the actual value has a boolean meaning. + + @returns: True if the actual value can be interpreted as a boolean + @rtype: boolean""" + + return (self.boolean != None) + + def __str__ (self): + return str(self.__value) + + def __repr__ (self): + return self.__str__() + + value = property(get,set) + +class ConfigParser: + """The newly implemented Config-Parser.""" + + # generates the complementary true-false-pairs + true_false = { + "true" : "false", + "1" : "0", + "on" : "off", + "yes" : "no", + "ja" : "nein", + "wahr" : "falsch"} + true_false.update(zip(true_false.values(), true_false.keys())) + + def __init__ (self, file): + """Constructor. + + @param file: the configuration file to open + @type file: string""" + + self.file = file + self.__initialize() + + def __initialize (self): + """Private method which initializes our dictionaries.""" + + self.vars = {"MAIN": {}} + self.cache = None # file cache + self.pos = {} # stores the positions of the matches + + def _invert (self, val): + """Invertes a given boolean. + + @param val: value to invert + @type val: string + @returns: inverted value + @rtype: string""" + + return self.true_false[val.lower()] + + def parse (self): + """Parses the file.""" + + # read into cache + file = open(self.file, "r") + self.cache = file.readlines() + file.close() + + # implicit first section is main + section = "MAIN" + count = -1 + for line in self.cache: + count += 1 + + ls = line.strip() + if not ls: continue # empty + if ls[0] in COMMENT: continue # comment + + # look for a section + match = SECTION.search(line) + if match: + sec = match.group(1).upper() + if sec != section: + self.vars[sec] = {} + section = sec + continue + + # look for an expression + match = EXPRESSION.search(line) + if match: + val = match.group(2) + + # find the boolean value + bool = None + if TRUE.match(val): + bool = True + elif FALSE.match(val): + bool = False + + # insert + key = match.group(1).lower() + self.vars[section][key] = Value(val, count, bool = bool) + self.pos[count] = match.span(2) + else: # neither comment nor empty nor expression nor section => error + debug("Unrecognized line:",line) + + def get (self, key, section = "MAIN"): + """Returns the value of a given key in a section. + + @param key: the key + @type key: string + @param section: the section + @type section: string + + @returns: value + @rtype: string + + @raises KeyError: if section or key could not be found""" + + section = section.upper() + key = key.lower() + return self.vars[section][key].value + + def get_boolean (self, key, section = "MAIN"): + """Returns the boolean value of a given key in a section. + + @param key: the key + @type key: string + @param section: the section + @type section: string + + @returns: value + @rtype: boolean + + @raises KeyError: if section or key could not be found + @raises ValueError: if key does not have a boolean value""" + + section = section.upper() + key = key.lower() + + val = self.vars[section][key] + + if val.is_bool(): + return val.boolean + + raise ValueError, "\"%s\" is not a boolean." % key + + def set (self, key, value = "", section = "MAIN"): + """Sets a new value of a given key in a section. + + @param key: the key + @type key: string + @param value: the new value + @type value: string + @param section: the section + @type section: string + + @raises KeyError: if section or key could not be found""" + + section = section.upper() + key = key.lower() + + self.vars[section][key].value = value + + def set_boolean (self, key, value, section = "MAIN"): + """Sets a new boolean value of a given key in a section. + Therefore it invertes the string representation of the boolean (in lowercase). + + @param key: the key + @type key: string + @param value: the new value + @type value: boolean + @param section: the section + @type section: string + + @raises KeyError: if section or key could not be found + @raises ValueError: if the old/new value is not a boolean""" + + section = section.upper() + key = key.lower() + + if not isinstance(value, types.BooleanType): + raise ValueError, "Passed value must be a boolean." + + val = self.vars[section][key] + if val.is_bool(): + if value is not val.boolean: + val.boolean = value + val.value = self._invert(val.value) + else: + raise ValueError, "\"%s\" is not a boolean." % key + + def write (self): + """Writes file.""" + + for sec in self.vars: + for key in self.vars[sec]: + val = self.vars[sec][key] + if val.changed: + part1 = self.cache[val.line][:self.pos[val.line][0]] # key+DELIMITER + part2 = val.value # value + part3 = self.cache[val.line][self.pos[val.line][1]:] # everything behind the vale (\n in normal cases) + self.cache[val.line] = part1 + part2 + part3 + + # write + f = open(self.file, "w") + f.writelines(self.cache) + f.close() + + # reload + self.__initialize() + self.parse() diff --git a/geneticone/gui/gtk/windows.py b/geneticone/gui/gtk/windows.py index 98bcba4..4b8cdda 100644 --- a/geneticone/gui/gtk/windows.py +++ b/geneticone/gui/gtk/windows.py @@ -209,9 +209,9 @@ class PreferenceWindow (AbstractDialog): """Sets all options in the Config-instance.""" for box in self.checkboxes: - self.cfg.set(\ + self.cfg.set_boolean(\ self.cfg.const[self.checkboxes[box]],\ - str(self.tree.get_widget(box).get_active())) + self.tree.get_widget(box).get_active()) for edit in self.edits: self.cfg.set(\ diff --git a/geneticone/gui/gui_helper.py b/geneticone/gui/gui_helper.py index 3e41501..d116877 100644 --- a/geneticone/gui/gui_helper.py +++ b/geneticone/gui/gui_helper.py @@ -15,13 +15,15 @@ from geneticone import backend from geneticone.backend import flags from geneticone.helper import * +# parser +from geneticone.config_parser import ConfigParser + # the wrapper from wrapper import Console, Tree # some stuff needed from subprocess import Popen, PIPE, STDOUT from threading import Thread -from ConfigParser import SafeConfigParser import pty class Config: @@ -42,25 +44,15 @@ class Config: def __init__ (self, cfgFile): """Constructor. - @attention: If cfgFile is a file, it is closed afterwards! - @param cfgFile: path to config file or file-object of the config-file. - @type cfgFile: string or file""" + @param cfgFile: path to config file + @type cfgFile: string""" # init ConfigParser - self._cfg = SafeConfigParser() + self._cfg = ConfigParser(cfgFile) - # set correct file-obj - if not isinstance(cfgFile, file): - self._file = open(cfgFile) # assume string - elif cfgFile.closed: - self._file = open(cfgFile.name) - else: - self._file = cfgFile - # read config - self._cfg.readfp(self._file) - self._file.close() + self._cfg.parse() # local configs self.local = {} @@ -75,7 +67,7 @@ class Config: @return: the option's value @rtype: string""" - return self._cfg.get(section, name) + return self._cfg.get(name, section) def get_boolean(self, name, section=const["main_sec"]): """Gets a boolean option. @@ -87,7 +79,7 @@ class Config: @return: the option's value @rtype: boolean""" - return self._cfg.getboolean(section, name) + return self._cfg.get_boolean(name, section) def modify_flags_config (self): """Sets the internal config of the L{flags}-module. @@ -150,16 +142,27 @@ class Config: @param name: name of the option @type name: string @param val: value to set the option to - @type val: string or boolean + @type val: string + @param section: section to look in; default is Main-Section + @type section: string""" + + self._cfg.set(name, val, section) + + def set_boolean (self, name, val, section=const["main_sec"]): + """Sets a boolean option. + + @param name: name of the option + @type name: string + @param val: value to set the option to + @type val: boolean @param section: section to look in; default is Main-Section @type section: string""" - self._cfg.set(section, name, val) + self._cfg.set_boolean(name, val, section) def write(self): """Writes to the config file and modify any external configs.""" - self._file = open(self._file.name,"w") - self._cfg.write(self._file) + self._cfg.write() self.modify_external_configs() class Database: -- cgit v1.2.3-70-g09d2