| 1 | # GNU MediaGoblin -- federated, autonomous media hosting |
| 2 | # Copyright (C) 2011 Free Software Foundation, Inc |
| 3 | # |
| 4 | # This program is free software: you can redistribute it and/or modify |
| 5 | # it under the terms of the GNU Affero General Public License as published by |
| 6 | # the Free Software Foundation, either version 3 of the License, or |
| 7 | # (at your option) any later version. |
| 8 | # |
| 9 | # This program is distributed in the hope that it will be useful, |
| 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | # GNU Affero General Public License for more details. |
| 13 | # |
| 14 | # You should have received a copy of the GNU Affero General Public License |
| 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 16 | |
| 17 | import os |
| 18 | import pkg_resources |
| 19 | |
| 20 | from configobj import ConfigObj, flatten_errors |
| 21 | from validate import Validator |
| 22 | |
| 23 | |
| 24 | CONFIG_SPEC_PATH = pkg_resources.resource_filename( |
| 25 | 'mediagoblin', 'config_spec.ini') |
| 26 | |
| 27 | |
| 28 | def _setup_defaults(config, config_path): |
| 29 | """ |
| 30 | Setup DEFAULTS in a config object from an (absolute) config_path. |
| 31 | """ |
| 32 | config.setdefault('DEFAULT', {}) |
| 33 | config['DEFAULT']['here'] = os.path.dirname(config_path) |
| 34 | config['DEFAULT']['__file__'] = config_path |
| 35 | |
| 36 | |
| 37 | def read_mediagoblin_config(config_path, config_spec=CONFIG_SPEC_PATH): |
| 38 | """ |
| 39 | Read a config object from config_path. |
| 40 | |
| 41 | Does automatic value transformation based on the config_spec. |
| 42 | Also provides %(__file__)s and %(here)s values of this file and |
| 43 | its directory respectively similar to paste deploy. |
| 44 | |
| 45 | This function doesn't itself raise any exceptions if validation |
| 46 | fails, you'll have to do something |
| 47 | |
| 48 | Args: |
| 49 | - config_path: path to the config file |
| 50 | - config_spec: config file that provides defaults and value types |
| 51 | for validation / conversion. Defaults to mediagoblin/config_spec.ini |
| 52 | |
| 53 | Returns: |
| 54 | A tuple like: (config, validation_result) |
| 55 | ... where 'conf' is the parsed config object and 'validation_result' |
| 56 | is the information from the validation process. |
| 57 | """ |
| 58 | config_path = os.path.abspath(config_path) |
| 59 | |
| 60 | config_spec = ConfigObj( |
| 61 | config_spec, |
| 62 | encoding='UTF8', list_values=False, _inspec=True) |
| 63 | |
| 64 | _setup_defaults(config_spec, config_path) |
| 65 | |
| 66 | config = ConfigObj( |
| 67 | config_path, |
| 68 | configspec=config_spec, |
| 69 | interpolation='ConfigParser') |
| 70 | |
| 71 | _setup_defaults(config, config_path) |
| 72 | |
| 73 | # For now the validator just works with the default functions, |
| 74 | # but in the future if we want to add additional validation/configuration |
| 75 | # functions we'd add them to validator.functions here. |
| 76 | # |
| 77 | # See also: |
| 78 | # http://www.voidspace.org.uk/python/validate.html#adding-functions |
| 79 | validator = Validator() |
| 80 | validation_result = config.validate(validator, preserve_errors=True) |
| 81 | |
| 82 | return config, validation_result |
| 83 | |
| 84 | |
| 85 | REPORT_HEADER = u"""\ |
| 86 | There were validation problems loading this config file: |
| 87 | -------------------------------------------------------- |
| 88 | """ |
| 89 | |
| 90 | |
| 91 | def generate_validation_report(config, validation_result): |
| 92 | """ |
| 93 | Generate a report if necessary of problems while validating. |
| 94 | |
| 95 | Returns: |
| 96 | Either a string describing for a user the problems validating |
| 97 | this config or None if there are no problems. |
| 98 | """ |
| 99 | report = [] |
| 100 | |
| 101 | # Organize the report |
| 102 | for entry in flatten_errors(config, validation_result): |
| 103 | # each entry is a tuple |
| 104 | section_list, key, error = entry |
| 105 | |
| 106 | if key is not None: |
| 107 | section_list.append(key) |
| 108 | else: |
| 109 | section_list.append(u'[missing section]') |
| 110 | |
| 111 | section_string = u':'.join(section_list) |
| 112 | |
| 113 | if error == False: |
| 114 | # We don't care about missing values for now. |
| 115 | continue |
| 116 | |
| 117 | report.append(u"%s = %s" % (section_string, error)) |
| 118 | |
| 119 | if report: |
| 120 | return REPORT_HEADER + u"\n".join(report) |
| 121 | else: |
| 122 | return None |