Skip to content

Instantly share code, notes, and snippets.

@stecman
Last active May 12, 2016 04:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stecman/2211f37593dfb17c4fa6 to your computer and use it in GitHub Desktop.
Save stecman/2211f37593dfb17c4fa6 to your computer and use it in GitHub Desktop.
heyday/silverstripe-slices 0.x to 1.0 config conversion script
#!/usr/bin/env python
# YAML config conversion script for heyday/silverstripe-slices 0.x going to 1.0
#
# Comments and whitespace aren't preserved - this is more to help you out by doing
# the repeatitive part of the conversion. The converted config is written to stdout.
#
# Note that this needs a python module installed to work:
#
# $ pip install pyyaml
#
# Once that's installed, use as:
#
# $ python slice-convert.py mysite/_config/slices.yml
from __future__ import print_function
import argparse
import copy
import os
import sys
import yaml
parser = argparse.ArgumentParser(description="Convert heyday/silverstripe-slices 0.x config to 1.0")
parser.add_argument("file", nargs='+', help="Path to a YAML file with 0.x slice config inside")
args = parser.parse_args()
# Section name slices will grouped under in the converted config
#
# The previous version of the config allowed multiple section names (classes), but
# this new config is really designed to be a single unit that covers all slices.
# This can be changed by the user after generation if needed.
BASE_SECTION = "Slice"
# Section properties that should be copied over as-is and ignored from the
VERBATIM_PROPS = [
"uploadFolder",
"uploadFolderFields",
"previewStylesheets",
"colorOptions"
]
# Section properties that aren't used in the new version of slices so can be left out
IGNORE_PROPS = [
"hiddenFields",
"uploadFolder",
"uploadFolderFields"
]
# Fields that had a specific use under the 0.x version of the slices module, but can
# be removed completely in the new config
LEGACY_FIELDS = [
"SecondaryIdentifier",
"TertiaryIdentifier"
]
# All property names have changed to be more appropriate for the new config structure
TEMPLATE_RENAME_MAP = {
"fieldLabels": "label",
"fieldHelp": "help",
"exampleFields": "exampleValue"
}
def is_verbatim_prop(key):
""" Check if a section property should be copied across as-is """
return key in VERBATIM_PROPS
def get_template_names(config):
""" Get all template names defined in a config (all sections)"""
names = []
for section in config:
for prop in config[section]:
for template in config[section][prop]:
if template != "Default" and prop not in VERBATIM_PROPS and prop not in IGNORE_PROPS:
names.append(template)
return sorted(set(names))
def strip_legacy_fields(source):
newList = copy.deepcopy(source)
for value in LEGACY_FIELDS:
if value in newList:
newList.remove(value)
return newList
def get_defaults(config):
""" Get a dictionary of the defaults across all properties """
defaults = {}
for section in config:
for prop in config[section]:
if "Default" in config[section][prop] and prop not in VERBATIM_PROPS and prop not in IGNORE_PROPS:
value = config[section][prop]["Default"]
if type(value) is list:
defaults[prop] = strip_legacy_fields(value)
else:
defaults[prop] = value
return defaults
def copy_config(config):
""" Create a new config, copying some keys verbatim from an existing one """
newConfig = {
BASE_SECTION: {
"templates": {}
}
}
for section in config:
for prop in config[section]:
if is_verbatim_prop(prop):
newConfig[BASE_SECTION][prop] = copy.deepcopy(config[section][prop])
return newConfig
def pivot_template_config(config, templateName, defaults={}):
""" Get a rearranged version of config with only the props (old names) grouped under a template name """
templateConfig = defaults.copy()
for section in config:
for prop in config[section]:
if prop not in VERBATIM_PROPS:
if templateName in config[section][prop]:
templateConfig[prop] = config[section][prop][templateName]
return templateConfig
def upgrade_config(config):
""" Pivot a 0.x slice module config to a 1.0 compatible one """
newConfig = copy_config(config)
templateCollection = newConfig[BASE_SECTION]["templates"]
defaults = get_defaults(config)
templates = get_template_names(config)
for templateName in templates:
pivoted = pivot_template_config(config, templateName, defaults)
templateConfig = {
"fields": {}
}
# Copy template nice names
if "secondaryIdentifierAsTemplatesMap" in pivoted:
templateConfig["name"] = pivoted["secondaryIdentifierAsTemplatesMap"]
# Copy class name map
if "secondaryIdentifierClassMap" in pivoted:
templateConfig["className"] = pivoted["secondaryIdentifierClassMap"]
if "tertiaryIdentifiers" in pivoted:
templateConfig["visualOptions"] = pivoted["tertiaryIdentifiers"]
# Add in show fields as empty (these don't have values to copy)
if "shownFields" in pivoted:
for field in pivoted["shownFields"]:
templateConfig["fields"][field] = {}
# Copy field specific configs into fields
for prop in TEMPLATE_RENAME_MAP:
if prop in pivoted:
for field in pivoted[prop]:
if field in LEGACY_FIELDS:
continue
if field not in templateConfig["fields"]:
templateConfig["fields"][field] = {}
templateConfig["fields"][field][TEMPLATE_RENAME_MAP[prop]] = pivoted[prop][field]
# Remove any fields specified as hidden
if "hiddenFields" in pivoted:
for field in pivoted["hiddenFields"]:
del templateConfig["fields"][field]
# Tidy up empty field configs
for field in templateConfig["fields"]:
fieldsConfig = templateConfig["fields"]
# Turn empty field configs into nulls
if not fieldsConfig[field]:
fieldsConfig[field] = None
# Change label-only field configs to use the label shortcut (string instead of object)
elif len(fieldsConfig[field].keys()) == 1 and "label" in fieldsConfig[field]:
fieldsConfig[field] = fieldsConfig[field]["label"]
# Put the converted config into the main config
templateCollection[templateName] = templateConfig
return newConfig
def nice_yaml_dump(data):
""" Dump YAML without serialising, and tidied up a little """
string = yaml.safe_dump(data, default_flow_style=False, width=2, indent=" ")
string = string.replace(": null\n", ": ~\n")
return string
for filename in args.file:
if not os.path.exists(filename):
print("Skippping non-existent file '%s'" % filename, file=sys.stderr)
continue
file = open(filename, 'r')
try:
config = yaml.load(file)
except:
print("Failed to parse %s as YAML" % filename, file=sys.stderr)
continue
newConfig = upgrade_config(config)
print(nice_yaml_dump(newConfig))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment