| Index: chainedconfigparser.py | 
| =================================================================== | 
| --- a/chainedconfigparser.py | 
| +++ b/chainedconfigparser.py | 
| @@ -15,16 +15,22 @@ | 
| # You should have received a copy of the GNU General Public License | 
| # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 
| # Note: These are the base functions common to all packagers, the actual | 
| # packagers are implemented in packagerGecko and packagerChrome. | 
| import os, codecs, ConfigParser | 
| +class Item(tuple): | 
| + def __new__(cls, name, value, source): | 
| + result = super(Item, cls).__new__(cls, (name, value)) | 
| + result.source = source | 
| + return result | 
| + | 
| class ChainedConfigParser: | 
| """ | 
| This class provides essentially the same interfaces as SafeConfigParser but | 
| allows chaining configuration files so that one config file provides the | 
| default values for the other. To specify the config file to inherit from | 
| a config file needs to contain the following option: | 
| [default] | 
| @@ -32,29 +38,33 @@ class ChainedConfigParser: | 
| The value of the inherit option has to be a relative path with forward | 
| slashes as delimiters. Up to 5 configuration files can be chained this way, | 
| longer chains are disallowed to deal with circular references. | 
| A main API difference to SafeConfigParser is the way a class instance is | 
| constructed: a file path has to be passed, this file is assumed to be | 
| encoded as UTF-8. Also, ChainedConfigParser data is read-only and the | 
| - options are case-sensitive. | 
| + options are case-sensitive. An additional option_source(section, option) | 
| + method is provided to get the path of the configuration file defining this | 
| + option (for relative paths). Items returned by the items() function also | 
| + have a source attribute serving the same purpose. | 
| """ | 
| def __init__(self, path): | 
| self.chain = [] | 
| self.read_path(path) | 
| def read_path(self, path): | 
| if len(self.chain) >= 5: | 
| raise Exception('Too much inheritance in config files') | 
| config = ConfigParser.SafeConfigParser() | 
| config.optionxform = str | 
| + config.source_path = path | 
| handle = codecs.open(path, 'rb', encoding='utf-8') | 
| config.readfp(handle) | 
| handle.close() | 
| self.chain.append(config) | 
| if config.has_section('default') and config.has_option('default', 'inherit'): | 
| parts = config.get('default', 'inherit').split('/') | 
| defaults_path = os.path.join(os.path.dirname(path), *parts) | 
| @@ -103,10 +113,16 @@ class ChainedConfigParser: | 
| def items(self, section): | 
| seen = set() | 
| result = [] | 
| for config in self.chain: | 
| if config.has_section(section): | 
| for name, value in config.items(section): | 
| if name not in seen: | 
| seen.add(name) | 
| - result.append((name, value)) | 
| + result.append(Item(name, value, config.source_path)) | 
| return result | 
| + | 
| + def option_source(self, section, option): | 
| + for config in self.chain: | 
| + if config.has_section(section) and config.has_option(section, option): | 
| + return config.source_path | 
| + raise ConfigParser.NoOptionError(option, section) |