Skip to content

Commit

Permalink
[#1804] Add config tool.
Browse files Browse the repository at this point in the history
  • Loading branch information
David Read committed Aug 12, 2014
1 parent d636428 commit 0527bce
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 0 deletions.
45 changes: 45 additions & 0 deletions ckan/lib/cli.py
Expand Up @@ -2334,3 +2334,48 @@ def create_grid_views(self):
logic.get_action('resource_view_create')(context, resource_view)

print '%s grid resource views created!' % count


class ConfigToolCommand(paste.script.command.Command):
'''Tool for editing options in a CKAN config file
paster config-tool <default.ini> option [option option ...]
e.g. paster config-tool sqlalchemy.url=123 'ckan.site_title=My Site'
'''
parser = paste.script.command.Command.standard_parser(verbose=True)
default_verbosity = 1
group_name = 'ckan'
usage = __doc__
summary = usage.split('\n')[0]

parser.add_option('-s', '--section', dest='section',
default='app:main', help='Section of the config file')
parser.add_option(
'-e', '--edit', action='store_true', dest='edit', default=False,
help='Checks the option already exists in the config file')

def command(self):
import config_tool
if len(self.args) < 2:
self.parser.error('Not enough arguments (got %i, need at least 2)'
% len(self.args))
config_filepath = self.args[0]
if not os.path.exists(config_filepath):
self.parser.error('Config filename %r does not exist.' %
config_filepath)
options = self.args[1:]
for option in options:
if '=' not in option:
sys.stderr.write(
'An option does not have an equals sign: %r '
'It should be \'key=value\'. If there are spaces you\'ll '
'need to quote the option.\n' % option)
sys.exit(1)
for option in options:
try:
config_tool.config_edit(config_filepath, self.options.section,
option, edit=self.options.edit)
except config_tool.ConfigToolError, e:
sys.stderr.write(e.message)
sys.exit(1)
97 changes: 97 additions & 0 deletions ckan/lib/config_tool.py
@@ -0,0 +1,97 @@
import re
import ConfigParser


def config_edit(config_filepath, section, option, edit=False):
config = ConfigParser.ConfigParser()
config.read(config_filepath)

# Parse the option
key, value = OPTION_RE.search(option).group('option', 'value')
key = key.strip()

# See if the key already exists in the file.
# Use ConfigParser as that is what Pylons will use to parse it
try:
config.get(section, key)
action = 'edit'
except ConfigParser.NoOptionError:
if edit:
raise ConfigToolError('Key "%s" does not exist in section "%s"' %
(key, section))
action = 'add'
except ConfigParser.NoSectionError:
action = 'add-section'

# Read & write the file, changing the value.
# (Can't just use config.set as it does not store all the comments and
# ordering)
with open(config_filepath, 'rb') as f:
input_lines = [line.rstrip('\n') for line in f]
output = config_edit_core(input_lines, section, key, value, action)
with open(config_filepath, 'wb') as f:
f.write('\n'.join(output) + '\n')


def config_edit_core(input_lines, section, key, value, action):
assert action in ('edit', 'add', 'add-section')
output = []
section_ = None

def write_option():
output.append('%s = %s' % (key, value))

for line in input_lines:
# leave blank and comment lines alone
if line.strip() == '' or line[0] in '#;':
output.append(line)
continue
if action == 'add':
# record the current section
section_match = SECTION_RE.match(line)
if section_match:
section_ = section_match.group('header')
output.append(line)
if section_ == section:
print 'Created option %s = "%s" (section "%s")' % \
(key, value, section)
write_option()
continue
elif action == 'edit':
# is it the option line we want to change
option_match = OPTION_RE.match(line)
if option_match:
key_, existing_value = option_match.group('option', 'value')
if key_.strip() == key:
if existing_value == value:
print 'Option unchanged %s = "%s" ' \
'(section "%s")' % \
(key, existing_value, section)
write_option()
continue
else:
print 'Edited option %s = "%s"->"%s" ' \
'(section "%s")' % \
(key, existing_value, value, section)
write_option()
continue
output.append(line)
if action == 'add-section':
output += ['', '[%s]' % section]
write_option()
print 'Created option %s = "%s" (NEW section "%s")' % \
(key, value, section)

return output


# Regexes same as in ConfigParser - OPTCRE & SECTCRE
# Expressing them here because they move between Python 2 and 3
OPTION_RE = re.compile(r'(?P<option>[^:=\s][^:=]*)'
r'\s*(?P<vi>[:=])\s*'
r'(?P<value>.*)$')
SECTION_RE = re.compile(r'\[(?P<header>.+)\]')


class ConfigToolError(Exception):
pass
48 changes: 48 additions & 0 deletions ckan/new_tests/lib/test_config_tool.py
@@ -0,0 +1,48 @@
from ckan.lib import config_tool


class TestConfigTool:
def test_edit(self):
config_lines = '''
[app:main]
ckan.site_title = CKAN
'''.split('\n')

out = config_tool.config_edit_core(
config_lines, 'app:main', 'ckan.site_title', 'New Title', 'edit')

assert out == '''
[app:main]
ckan.site_title = New Title
'''.split('\n'), out

def test_new(self):
config_lines = '''
[app:main]
ckan.site_title = CKAN
'''.split('\n')

out = config_tool.config_edit_core(
config_lines, 'app:main', 'ckan.option', 'New stuff', 'add')

assert out == '''
[app:main]
ckan.option = New stuff
ckan.site_title = CKAN
'''.split('\n'), out

def test_new_section(self):
config_lines = '''
[app:main]
ckan.site_title = CKAN'''.split('\n')

out = config_tool.config_edit_core(
config_lines,
'logger', 'keys', 'root, ckan, ckanext', 'add-section')

assert out == '''
[app:main]
ckan.site_title = CKAN
[logger]
keys = root, ckan, ckanext'''.split('\n'), out
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -40,6 +40,7 @@
'datastore = ckanext.datastore.commands:SetupDatastoreCommand',
'front-end-build = ckan.lib.cli:FrontEndBuildCommand',
'views = ckan.lib.cli:ViewsCommand',
'config-tool = ckan.lib.cli:ConfigToolCommand',
],
'console_scripts': [
'ckan-admin = bin.ckan_admin:Command',
Expand Down

0 comments on commit 0527bce

Please sign in to comment.