Last active
October 27, 2021 16:25
-
-
Save onmyway133/c486939f82fc4d3a8ed4be21538fdd32 to your computer and use it in GitHub Desktop.
AutoLayoutConverter - π Convert Cartography to NSLayoutAnchor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.DS_Store | |
.tags* | |
/.idea/ | |
/build/ | |
/dist/ | |
/external_binaries/ | |
/out/ | |
/vendor/download/ | |
/vendor/debian_jessie_amd64-sysroot/ | |
/vendor/debian_jessie_arm-sysroot/ | |
/vendor/debian_jessie_arm64-sysroot/ | |
/vendor/debian_jessie_i386-sysroot/ | |
/vendor/debian_wheezy_amd64-sysroot/ | |
/vendor/debian_wheezy_arm-sysroot/ | |
/vendor/debian_wheezy_i386-sysroot/ | |
/vendor/python_26/ | |
/vendor/npm/ | |
/vendor/llvm/ | |
/vendor/llvm-build/ | |
/vendor/.gclient | |
node_modules/ | |
*.xcodeproj | |
*.swp | |
*.pyc | |
*.VC.db | |
*.VC.VC.opendb | |
.vs/ | |
.vscode/ | |
*.vcxproj | |
*.vcxproj.user | |
*.vcxproj.filters | |
*.sln | |
*.log | |
/brightray/brightray.opensdf | |
/brightray/brightray.sdf | |
/brightray/brightray.sln | |
/brightray/brightray.vcxproj* | |
/brightray/brightray.suo | |
/brightray/brightray.v12.suo | |
/brightray/brightray.xcodeproj/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Glob = require('glob') | |
const Fs = require('fs') | |
function main() { | |
const projectPath = getProjectPath() | |
const files = getAllFiles(projectPath) | |
files.forEach((file) => { | |
handleFile(file) | |
}) | |
} | |
/// The 1st argument is node, 2nd is index.js, 3rd is path | |
function getProjectPath() { | |
return process.argv[2] | |
} | |
/// Only select `swift` file | |
function getAllFiles(projectPath) { | |
if (projectPath.endsWith('.swift')) { | |
return [projectPath] | |
} else { | |
const files = Glob.sync(`${projectPath}/**/*.swift`) | |
return files | |
} | |
} | |
/// Read file, replace by matches, and write again | |
function handleFile(file) { | |
console.log(file) | |
Fs.readFile(file, 'utf8', (error, data) => { | |
// non greedy `*?` | |
const pattern = '(.)*constrain\\(.+\\) {\\s(\\s|.)*?\\n(\\s)*}' | |
const regex = new RegExp(pattern) | |
let matches = data.match(regex) | |
// RegEx only return the 1st match per execution, let's do a recursion | |
while (matches != null) { | |
const match = matches[0] | |
const indentationLength = findIndentationLength(match) | |
const rawTransforms = handleMatch(match) | |
const transforms = handleSuperview(rawTransforms, match) | |
const statements = handleTransforms(transforms, indentationLength) | |
const string = handleStatements(statements, indentationLength) | |
data = data.replace(match, string) | |
// Examine again | |
matches = data.match(regex) | |
} | |
// Handle import | |
data = handleImport(data) | |
Fs.writeFile(file, data, 'utf8') | |
}) | |
} | |
/// Replace or remove import | |
function handleImport(data) { | |
if (data.includes('import Sugar')) { | |
return data.replace('import Cartography', '') | |
} else { | |
return data.replace('import Cartography', 'import Sugar') | |
} | |
} | |
/// Format transform to have `,` and linebreaks | |
function handleTransforms(transforms, indentationLength) { | |
let string = '' | |
// Expect the first is always line break | |
if (transforms[0] != '\n') { | |
transforms.unshift('\n') | |
} | |
const indentation = makeIndentation(indentationLength + 2) | |
transforms.forEach((line, index) => { | |
if (line.length > 1) { | |
string = string.concat(indentation + line) | |
if (index < transforms.length - 1) { | |
string = string.concat(',\n') | |
} | |
} else { | |
string = string.concat('\n') | |
} | |
}) | |
return string | |
} | |
/// Replace superView | |
function handleSuperview(transforms, match) { | |
const superView = findSuperview(match) | |
if (superView == null) { | |
return transforms | |
} | |
const superViewPattern = ' superView.' | |
transforms = transforms.map((transform) => { | |
if (transform.includes(superViewPattern)) { | |
return transform.replace(superViewPattern, ` ${superView}.`) | |
} else { | |
return transform | |
} | |
}) | |
return transforms | |
} | |
/// Embed the statements inside `Constraint.on` | |
function handleStatements(statements, indentationLength) { | |
const indentation = makeIndentation(indentationLength) | |
return `${indentation}Constraint.on(${statements}\n${indentation})` | |
} | |
/// Turn every line into flatten transformed line | |
function handleMatch(match) { | |
let lines = match.split('\n') | |
lines = lines.map((line) => { | |
return handleLine(line.trim()) | |
}).filter((transforms) => { | |
return transforms != null | |
}) | |
let flatten = [].concat.apply([], lines) | |
return flatten | |
} | |
/// Check to handle lines, turn them into `LayoutAnchor` statements | |
function handleLine(line) { | |
const itemPattern = '\\w*\\.\\w* (=|<|>)= \\w*\\.*\\..*' | |
const sizePattern = '\w*\.\w* (=|<|>)= (\s|.)*' | |
if (line.includes('edges == ')) { | |
return handleEdges(line) | |
} else if (line.includes('center == ')) { | |
return handleCenter(line) | |
} else if (hasPattern(line, itemPattern) && !hasSizeKeywords(line)) { | |
return handleItem(line) | |
} else if (hasPattern(line, sizePattern)) { | |
return handleSize(line) | |
} else if (line.includes('>=') || line.includes('>=')) { | |
return line | |
} else if (line.includes('.')) { | |
// return the line itself to let the human fix | |
return line | |
} else if (line.length == 0) { | |
return ['\n'] | |
} else { | |
return null | |
} | |
} | |
/// For ex: listView.bottom == listView.superview!.bottom - 43 | |
/// listView.bottom == listView.superview!.bottom - Metrics.BackButtonWidth | |
function handleItem(line) { | |
const equalSign = getEqualSign(line) | |
const parts = line.split(` ${equalSign} `) | |
const left = parts[0] | |
let rightParts = parts[1].trim() | |
let right = rightParts.split(' ')[0] | |
let number = rightParts.replace(right, '').replace('- ', '-').trim() | |
if (number.startsWith('+ ')) { | |
number = number.slice(2) | |
} | |
let equal = getEqual(line) | |
if (number == null || number.length == 0 ) { | |
return [ | |
`${left}Anchor.constraint(${equal}: ${right}Anchor)` | |
] | |
} else { | |
return [ | |
`${left}Anchor.constraint(${equal}: ${right}Anchor, constant: ${number})` | |
] | |
} | |
} | |
/// For ex: segmentedControl.height == 24 | |
/// backButton.width == Metrics.BackButtonWidth | |
function handleSize(line) { | |
const equalSign = getEqualSign(line) | |
const parts = line.split(` ${equalSign} `) | |
const left = parts[0] | |
const right = parts[1] | |
let equal = getEqual(line) | |
return [ | |
`${left}Anchor.constraint(${equal}Constant: ${right})` | |
] | |
} | |
/// For ex: mapView.edges == listView.edges | |
function handleEdges(line) { | |
const parts = line.split(' == ') | |
const left = parts[0].split('.')[0] | |
const right = removeLastPart(parts[1]) | |
return [ | |
`${left}.topAnchor.constraint(equalTo: ${right}.topAnchor)`, | |
`${left}.bottomAnchor.constraint(equalTo: ${right}.bottomAnchor)`, | |
`${left}.leftAnchor.constraint(equalTo: ${right}.leftAnchor)`, | |
`${left}.rightAnchor.constraint(equalTo: ${right}.rightAnchor)` | |
] | |
} | |
/// For ex: mapView.center == listView.center | |
function handleCenter(line) { | |
const parts = line.split(' == ') | |
const left = parts[0].split('.')[0] | |
const right = removeLastPart(parts[1]) | |
return [ | |
`${left}.centerXAnchor.constraint(equalTo: ${right}.centerXAnchor)`, | |
`${left}.centerYAnchor.constraint(equalTo: ${right}.centerYAnchor)` | |
] | |
} | |
function hasPattern(string, pattern) { | |
const regex = new RegExp(pattern) | |
let matches = string.match(regex) | |
if (matches == null) { | |
return false | |
} | |
matches = matches.filter((match) => { | |
return match !== undefined && match.length > 1 | |
}) | |
return matches.length > 0 | |
} | |
function removeLastPart(string) { | |
const parts = string.split('.') | |
parts.pop() | |
return parts.join('.') | |
} | |
function getEqual(line) { | |
if (line.includes('==')) { | |
return 'equalTo' | |
} else if (line.includes('<=')) { | |
return 'lessThanOrEqualTo' | |
} else { | |
return 'greaterThanOrEqualTo' | |
} | |
} | |
function getEqualSign(line) { | |
if (line.includes('==')) { | |
return '==' | |
} else if (line.includes('>=')) { | |
return '>=' | |
} else { | |
return '<=' | |
} | |
} | |
function findIndentationLength(match) { | |
return match.split('constrain')[0].length + 1 | |
} | |
function makeIndentation(length) { | |
return Array(length).join(' ') | |
} | |
// For ex: constrain(tableView, headerView, view) { tableView, headerView, superView in | |
function findSuperview(match) { | |
const line = match.split('\n')[0] | |
if (!line.includes(' superView in')) { | |
return null | |
} | |
const pattern = ', \\w*\\)' | |
const regex = RegExp(pattern) | |
const string = line.match(regex)[0] // `, view) | |
return string.replace(', ', '').replace(')', '') | |
} | |
// Check special size keywords | |
function hasSizeKeywords(line) { | |
const keywords = ['Metrics', 'Dimensions', 'UIScreen'] | |
return keywords.filter((keyword) => { | |
return line.includes(keyword) | |
}).length != 0 | |
} | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "AutoLayoutConverter", | |
"version": "1.0.0", | |
"description": "Convert Cartography to LayoutAnchor", | |
"main": "index.js", | |
"author": "Khoa Pham", | |
"license": "MIT", | |
"dependencies": { | |
"glob": "^7.1.2" | |
}, | |
"scripts": { | |
"start": "node index.js" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. | |
# yarn lockfile v1 | |
balanced-match@^1.0.0: | |
version "1.0.0" | |
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" | |
brace-expansion@^1.1.7: | |
version "1.1.8" | |
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" | |
dependencies: | |
balanced-match "^1.0.0" | |
concat-map "0.0.1" | |
concat-map@0.0.1: | |
version "0.0.1" | |
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" | |
fs.realpath@^1.0.0: | |
version "1.0.0" | |
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" | |
glob@^7.1.2: | |
version "7.1.2" | |
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" | |
dependencies: | |
fs.realpath "^1.0.0" | |
inflight "^1.0.4" | |
inherits "2" | |
minimatch "^3.0.4" | |
once "^1.3.0" | |
path-is-absolute "^1.0.0" | |
inflight@^1.0.4: | |
version "1.0.6" | |
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" | |
dependencies: | |
once "^1.3.0" | |
wrappy "1" | |
inherits@2: | |
version "2.0.3" | |
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" | |
minimatch@^3.0.4: | |
version "3.0.4" | |
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" | |
dependencies: | |
brace-expansion "^1.1.7" | |
once@^1.3.0: | |
version "1.4.0" | |
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" | |
dependencies: | |
wrappy "1" | |
path-is-absolute@^1.0.0: | |
version "1.0.1" | |
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" | |
wrappy@1: | |
version "1.0.2" | |
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Feedback:
self
is in theconstrain
input, it can not replaced and cause compiler error.I guess the issue occurs when there is a class constant in the Cartography code.when a.
inconstraint
input the script can not handle)~
operator correctlyinset
align(top/bottom: var1, var2, var3...)
(the lines are removed)