Skip to content

Instantly share code, notes, and snippets.

@esteele
Last active February 16, 2018 18:45
Show Gist options
  • Save esteele/0f721f899fe53f7b80445db6470f8933 to your computer and use it in GitHub Desktop.
Save esteele/0f721f899fe53f7b80445db6470f8933 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import re
import sys
import os
from collections import OrderedDict
import glob
import xml.etree.ElementTree as ET
def getMembersForElement(path, file_pattern, regexp, include_file_name=False):
""" Parse the contents of a metadata file to find member entries for package.xml """
members = []
for filename in glob.iglob(os.path.join(path, file_pattern)):
object_name = os.path.splitext(os.path.basename(filename))[0]
with open(filename, 'r') as f:
contents = f.read()
p = re.compile(regexp)
for r in p.findall(contents):
if include_file_name:
members.append('%s.%s' % (object_name, r))
else:
members.append(r)
members.sort()
return members
def getMembersByFilePattern(src_path, subdirectory, file_pattern, include_extension=False):
members = []
base_dir = os.path.join(src_path, subdirectory)
for filename in glob.iglob(os.path.join(base_dir, file_pattern)):
if '-meta.xml' in filename:
continue
filename = os.path.relpath(filename, base_dir)
if not include_extension:
filename = os.path.splitext(filename)[0]
members.append(filename)
members.sort()
return members
def getCustomObjects(src_path, subdirectory, file_pattern, include_extension=False):
members = []
base_dir = os.path.join(src_path, subdirectory)
for filename in glob.iglob(os.path.join(base_dir, file_pattern)):
if '-meta.xml' in filename:
continue
if not ET.parse(filename).getroot().findall('{http://soap.sforce.com/2006/04/metadata}label'):
continue
filename = os.path.relpath(filename, base_dir)
if not include_extension:
filename = os.path.splitext(filename)[0]
members.append(filename)
members.sort()
return members
def generatePackageXMLFromDirectory(metadata_path):
# Map metadata types onto rules for finding package.xml values
elements = OrderedDict([
# ('ApexClass', getMembersByFilePattern(metadata_path, 'classes', '*.cls')),
('ApexClass', ['*']),
# ('ApexComponent', getMembersByFilePattern(metadata_path, 'components', '*.component')),
('ApexComponent', ['*']),
# ('ApexPage', getMembersByFilePattern(metadata_path, 'pages', '*.page')),
('ApexPage', ['*']),
# ('ApexTrigger', getMembersByFilePattern(metadata_path, 'triggers', '*.trigger')),
('ApexTestSuite', getMembersByFilePattern(metadata_path, 'testSuites', '*.testSuite')),
('ApexTrigger', ['*']),
('AssignmentRules', getMembersByFilePattern(metadata_path, 'assignmentRules', '*.assignmentRules')),
('AutoResponseRules', getMembersByFilePattern(metadata_path, 'autoResponseRules', '*.autoResponseRules')),
('AuraDefinitionBundle', getMembersByFilePattern(metadata_path, 'aura', '*')),
('BusinessProcess', getMembersForElement(metadata_path, 'objects/*.object', 'businessProcesses>\\s*<fullName>(.*)(?=</fullName>)', True)),
('Community', getMembersByFilePattern(metadata_path, 'communities', '*.community')),
('CompactLayout', getMembersForElement(metadata_path, 'objects/*.object', 'compactLayouts>\\s*<fullName>(.*)(?=</fullName>)', True)),
('CustomApplication', getMembersByFilePattern(metadata_path, 'applications', '*.app')),
('CustomField', getMembersForElement(metadata_path, 'objects/*.object', 'fields>\\s*<fullName>(.*)(?=</fullName>)', True)),
('CustomLabel', getMembersForElement(metadata_path, 'labels/CustomLabels.labels', 'labels>\\s*<fullName>(.*)(?=</fullName>)')),
('CustomObject', getCustomObjects(metadata_path, 'objects', '*.object')),
('CustomObjectTranslation', getMembersByFilePattern(metadata_path, 'objectTranslations', '*.objectTranslation')),
('CustomPermission', getMembersByFilePattern(metadata_path, 'customPermissions', '*.customPermission')),
('CustomTab', getMembersByFilePattern(metadata_path, 'tabs', '*.tab')),
('Document', getMembersByFilePattern(metadata_path, 'documents', '*/*', True)),
('DuplicateRule', getMembersByFilePattern(metadata_path, 'duplicateRules', '*.duplicateRule')),
('EmailTemplate', getMembersByFilePattern(metadata_path, 'email', '*/*.email', False)),
('FieldSet', getMembersForElement(metadata_path, 'objects/*.object', 'fieldSets>\\s*<fullName>(.*)(?=</fullName>)', True)),
('FlexiPage', getMembersByFilePattern(metadata_path, 'flexipages', '*.flexipage')),
('Flow', getMembersByFilePattern(metadata_path, 'flows', '*.flow')),
('FlowDefinition', getMembersByFilePattern(metadata_path, 'flowDefinitions', '*.flowDefinition')),
('GlobalValueSet', getMembersByFilePattern(metadata_path, 'globalValueSets', '*.globalValueSet')),
('Group', getMembersByFilePattern(metadata_path, 'groups', '*.group')),
('Layout', getMembersByFilePattern(metadata_path, 'layouts', '*.layout')),
('ListView', getMembersForElement(metadata_path, 'objects/*.object', 'listViews>\\s*<fullName>(.*)(?=</fullName>)', True)),
('MatchingRule', getMembersForElement(metadata_path, 'matchingRules/*.matchingRule', 'matchingRules>\\s*<fullName>(.*)(?=</fullName>)', True)),
('Network', getMembersByFilePattern(metadata_path, 'networks' , '*.network')),
('PermissionSet', getMembersByFilePattern(metadata_path, 'permissionsets' , '*.permissionset')),
('Profile', getMembersByFilePattern(metadata_path, 'profiles', '*.profile')),
('QuickAction', getMembersByFilePattern(metadata_path, 'quickActions', '*.quickAction')),
('Queue', getMembersByFilePattern(metadata_path, 'queues', '*.queue')),
('RecordType', getMembersForElement(metadata_path, 'objects/*.object', 'recordTypes>\\s*<fullName>(.*)(?=</fullName>)', True)),
('SharingCriteriaRule', getMembersForElement(metadata_path, 'sharingRules/*.sharingRules', 'sharingCriteriaRules>\\s*<fullName>(.*)(?=</fullName>)', True)),
('SharingOwnerRule', getMembersForElement(metadata_path, 'sharingRules/*.sharingRules', 'sharingOwnerRules>\\s*<fullName>(.*)(?=</fullName>)', True)),
('SharingTerritoryRule', getMembersForElement(metadata_path, 'sharingRules/*.sharingRules', 'sharingTerritoryRules>\\s*<fullName>(.*)(?=</fullName>)', True)),
('StandardValueSet', getMembersByFilePattern(metadata_path, 'standardValueSets', '*')),
# ('StaticResource', getMembersByFilePattern(metadata_path, 'staticresources', '*.resource')),
('StaticResource', ['*']),
('Translations', getMembersByFilePattern(metadata_path, 'translations', '*.translation')),
('ValidationRule', getMembersForElement(metadata_path, 'objects/*.object', 'validationRules>\\s*<fullName>(.*)(?=</fullName>)', True)),
('WebLink', getMembersForElement(metadata_path, 'objects/*.object', 'webLinks>\\s*<fullName>(.*)(?=</fullName>)', True)),
('Workflow', getMembersByFilePattern(metadata_path, 'workflows', '*.workflow'))
])
# Write a new package.xml file based on the values found in the individiual metadata files
with open(os.path.join(metadata_path, 'package.xml'), 'w') as packagexml:
packagexml.write('<?xml version="1.0" encoding="UTF-8"?>\n')
packagexml.write('<Package xmlns="http://soap.sforce.com/2006/04/metadata">\n')
for typename, members in elements.items():
if members:
packagexml.write(' <types>\n')
for m in members:
packagexml.write(' <members>%s</members>\n' % m)
packagexml.write(' <name>%s</name>\n' % typename)
packagexml.write(' </types>\n')
packagexml.write(' <version>40.0</version>\n')
packagexml.write('</Package>\n')
def generateTestSuites(metadata_path):
regexp = r"@group (.*)(?=\n)"
suites = []
for filename in glob.glob(os.path.join(metadata_path, 'classes', '*.cls')):
object_name = os.path.splitext(os.path.basename(filename))[0]
with open(filename, 'r') as f:
contents = f.read()
if '@istest' in contents.lower():
p = re.compile(regexp)
group = p.findall(contents)
if not group:
print("WARNING: No suite specified for %s. Adding to 'TBD'" % object_name)
suite_name = 'TBD'
else:
suite_name = group[0]
suites.append((suite_name, object_name))
suites = sorted(suites, key=lambda tup: tup[0])
from itertools import groupby
for suite, classes in groupby(suites, lambda x: x[0]):
suite = suite.replace(' ', '')
suite = suite.replace('>', '_')
sortedclasses = [a for a in classes]
sortedclasses.sort()
print("%s" % suite)
with open(os.path.join(metadata_path, 'testSuites', '%s.testSuite' % suite), 'w+') as suitefile:
suitefile.write('<?xml version="1.0" encoding="UTF-8"?>\n')
suitefile.write('<ApexTestSuite xmlns="http://soap.sforce.com/2006/04/metadata">\n')
for name in sortedclasses:
suitefile.write(" <testClassName>%s</testClassName>\n" % name[1])
suitefile.write('</ApexTestSuite>')
def generateNonSSOProfile(profile_path):
""" Given a path to a profile, create a non-sso version """
result_filename = profile_path.replace('.profile', ' - Non SSO.profile')
with open(profile_path, 'r') as original_profile:
with open(result_filename, 'w') as nonsso_profile:
pattern = r'(<userPermissions>\s*<enabled>true</enabled>\s*<name>IsSsoEnabled</name>\s*</userPermissions>\n)'
disabled_sso = re.sub(pattern, '', original_profile.read())
nonsso_profile.write(disabled_sso)
if __name__ == "__main__":
if len(sys.argv) == 2:
generatePackageXMLFromDirectory(sys.argv[1])
else:
# generateNonSSOProfile('./src/profiles/Donor on Platform.profile')
# generateNonSSOProfile('./src/profiles/Foundation Employee w%2F Volunteering Admin.profile')
# generateTestSuites('./src')
generatePackageXMLFromDirectory('./src')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment