| // Copyright 2022 Google LLC |
| |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License.import { exec } from 'child_process'; |
| |
| import DOM from './helpers/DOMFuncs'; |
| import { FileIO } from './helpers/FileIO'; |
| import { loadMIgrationList } from './helpers/migrationList'; |
| import { processQueriedEntries, TEvalExistingEntry } from './helpers/processXML'; |
| import { repoPath } from './helpers/rootPath'; |
| import { groupReplace } from './helpers/textFuncs'; |
| |
| async function init() { |
| const migrationMap = await loadMIgrationList(); |
| const basePath = `${repoPath}/../tm-qpr-dev/frameworks/base/core/res/res/values/`; |
| |
| await processQueriedEntries(migrationMap, { |
| containerQuery: 'declare-styleable[name="Theme"]', |
| hidable: true, |
| path: `${basePath}attrs.xml`, |
| step: 0, |
| tagName: 'attr', |
| evalExistingEntry: (_attrValue, migItem, qItem) => { |
| const { hidden, textContent: currentComment } = DOM.getElementComment(qItem); |
| |
| if (hidden) migItem.isHidden = hidden; |
| |
| const { newComment } = migItem; |
| return [ |
| hidden ? 'update' : 'duplicate', |
| { |
| attrs: { name: migItem.replaceToken }, |
| ...(newComment |
| ? { comment: `${newComment} @hide ` } |
| : currentComment |
| ? { comment: hidden ? currentComment : `${currentComment} @hide ` } |
| : {}), |
| }, |
| ]; |
| }, |
| evalMissingEntry: (_originalToken, { replaceToken, newComment }) => { |
| return { |
| tagName: 'attr', |
| attrs: { |
| name: replaceToken, |
| format: 'color', |
| }, |
| comment: `${newComment} @hide `, |
| }; |
| }, |
| }); |
| |
| // only update all existing entries |
| await processQueriedEntries(migrationMap, { |
| tagName: 'item', |
| path: `${basePath}themes_device_defaults.xml`, |
| containerQuery: 'resources', |
| step: 2, |
| evalExistingEntry: (_attrValue, { isHidden, replaceToken, step }, _qItem) => { |
| if (step[0] != 'ignore') |
| return [ |
| isHidden ? 'update' : 'duplicate', |
| { |
| attrs: { name: replaceToken }, |
| }, |
| ]; |
| }, |
| }); |
| |
| // add missing entries on specific container |
| await processQueriedEntries(migrationMap, { |
| tagName: 'item', |
| path: `${basePath}themes_device_defaults.xml`, |
| containerQuery: 'resources style[parent="Theme.Material"]', |
| step: 3, |
| evalMissingEntry: (originalToken, { newDefaultValue, replaceToken }) => { |
| return { |
| tagName: 'item', |
| content: newDefaultValue, |
| attrs: { |
| name: replaceToken, |
| }, |
| }; |
| }, |
| }); |
| |
| const evalExistingEntry: TEvalExistingEntry = (_attrValue, { replaceToken, step }, _qItem) => { |
| if (step[0] == 'update') |
| return [ |
| 'update', |
| { |
| attrs: { name: replaceToken }, |
| }, |
| ]; |
| }; |
| |
| await processQueriedEntries(migrationMap, { |
| tagName: 'item', |
| containerQuery: 'resources', |
| path: `${basePath}../values-night/themes_device_defaults.xml`, |
| step: 4, |
| evalExistingEntry, |
| }); |
| |
| await processQueriedEntries(migrationMap, { |
| tagName: 'java-symbol', |
| path: `${basePath}symbols.xml`, |
| containerQuery: 'resources', |
| step: 5, |
| evalExistingEntry, |
| }); |
| |
| // update attributes on tracked XML files |
| { |
| const searchAttrs = [ |
| 'android:color', |
| 'android:indeterminateTint', |
| 'app:tint', |
| 'app:backgroundTint', |
| 'android:background', |
| 'android:tint', |
| 'android:drawableTint', |
| 'android:textColor', |
| 'android:fillColor', |
| 'android:startColor', |
| 'android:endColor', |
| 'name', |
| 'ns1:color', |
| ]; |
| |
| const filtered = new Map( |
| [...migrationMap] |
| .filter(([_originalToken, { step }]) => step[0] == 'update') |
| .map(([originalToken, { replaceToken }]) => [originalToken, replaceToken]) |
| ); |
| |
| const query = |
| searchAttrs.map((str) => `*[${str}]`).join(',') + |
| [...filtered.keys()].map((originalToken) => `item[name*="${originalToken}"]`).join(','); |
| |
| const trackedFiles = await FileIO.loadFileList( |
| `${__dirname}/resources/whitelist/xmls1.json` |
| ); |
| |
| const promises = trackedFiles.map(async (locaFilePath) => { |
| const filePath = `${repoPath}/${locaFilePath}`; |
| |
| const doc = await FileIO.loadXML(filePath); |
| const docUpdated = DOM.replaceStringInAttributeValueOnQueried( |
| doc.documentElement, |
| query, |
| searchAttrs, |
| filtered |
| ); |
| if (docUpdated) { |
| await FileIO.saveFile(DOM.XMLDocToString(doc), filePath); |
| } else { |
| console.warn(`Failed to update tracked file: '${locaFilePath}'`); |
| } |
| }); |
| await Promise.all(promises); |
| } |
| |
| // updates tag content on tracked files |
| { |
| const searchPrefixes = ['?android:attr/', '?androidprv:attr/']; |
| const filtered = searchPrefixes |
| .reduce<Array<[string, string]>>((acc, prefix) => { |
| return [ |
| ...acc, |
| ...[...migrationMap.entries()] |
| .filter(([_originalToken, { step }]) => step[0] == 'update') |
| .map( |
| ([originalToken, { replaceToken }]) => |
| [`${prefix}${originalToken}`, `${prefix}${replaceToken}`] as [ |
| string, |
| string |
| ] |
| ), |
| ]; |
| }, []) |
| .sort((a, b) => b[0].length - a[0].length); |
| |
| const trackedFiles = await FileIO.loadFileList( |
| `${__dirname}/resources/whitelist/xmls2.json` |
| ); |
| |
| const promises = trackedFiles.map(async (locaFilePath) => { |
| const filePath = `${repoPath}/${locaFilePath}`; |
| const doc = await FileIO.loadXML(filePath); |
| const docUpdated = DOM.replaceContentTextOnQueried( |
| doc.documentElement, |
| 'item, color', |
| filtered |
| ); |
| if (docUpdated) { |
| await FileIO.saveFile(DOM.XMLDocToString(doc), filePath); |
| } else { |
| console.warn(`Failed to update tracked file: '${locaFilePath}'`); |
| } |
| }); |
| await Promise.all(promises); |
| } |
| |
| // replace imports on Java / Kotlin |
| { |
| const replaceMap = new Map( |
| [...migrationMap.entries()] |
| .filter(([_originalToken, { step }]) => step[0] == 'update') |
| .map( |
| ([originalToken, { replaceToken }]) => |
| [originalToken, replaceToken] as [string, string] |
| ) |
| .sort((a, b) => b[0].length - a[0].length) |
| ); |
| |
| const trackedFiles = await FileIO.loadFileList( |
| `${__dirname}/resources/whitelist/java.json` |
| ); |
| |
| const promises = trackedFiles.map(async (locaFilePath) => { |
| const filePath = `${repoPath}/${locaFilePath}`; |
| const fileContent = await FileIO.loadFileAsText(filePath); |
| const str = groupReplace(fileContent, replaceMap, 'R.attr.(#group#)(?![a-zA-Z])'); |
| await FileIO.saveFile(str, filePath); |
| }); |
| await Promise.all(promises); |
| } |
| } |
| |
| init(); |