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) |