forked from desktop/desktop
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgenerate-octicons.ts
134 lines (108 loc) · 3.56 KB
/
generate-octicons.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/* generate-octicons
*
* Utility script for generating a strongly typed representation of all
* octicons distributed by the octicons NPM package. Enumerates the icons
* and generates the TypeScript class containing just what Desktop needs.
*/
import * as fs from 'fs'
import * as Path from 'path'
import * as cp from 'child_process'
import { check } from 'reserved-words'
import xml2js from 'xml2js'
import toCamelCase from 'to-camel-case'
interface IXML2JSNode {
path: {
$: {
d: string
'fill-rule'?: string
}
}
}
interface IOcticonData {
readonly jsFriendlyName: string
readonly pathData: string
readonly width: string
readonly height: string
readonly fillRule?: string
}
const viewBoxRe = /0 0 (\d+) (\d+)/
function readXml(xml: string): Promise<IXML2JSNode> {
return new Promise((resolve, reject) => {
xml2js.parseString(xml, function (err, result: IXML2JSNode) {
if (err) {
reject(err)
} else {
resolve(result)
}
})
})
}
function getJsFriendlyName(name: string) {
const sanitizedName = toCamelCase(name)
return check(sanitizedName, 'es6', true) ? sanitizedName + '_' : sanitizedName
}
async function generateIconData(): Promise<ReadonlyArray<IOcticonData>> {
const octicons = require('@primer/octicons')
const results = new Array<IOcticonData>()
for (const name of Object.keys(octicons).sort()) {
const octicon = octicons[name]
if (octicon.heights.length === 0) {
throw new Error(`Unexpected empty sizes array for ${octicon.symbol}`)
}
// Try to get the 16px version of the SVG data if it exists,
// if it doesn't just fallback to the version that is defined.
const octiconData =
octicon.heights['16'] ?? octicon.heights[Object.keys(octicon.heights)[0]]
const viewBox = octiconData.options.viewBox
const viewBoxMatch = viewBoxRe.exec(viewBox)
if (!viewBoxMatch) {
throw new Error(
`Unexpected viewBox format for ${octicon.symbol} - '${viewBox}'`
)
}
const [, width, height] = viewBoxMatch
const result = await readXml(octiconData.path)
const pathData = result.path.$.d
const jsFriendlyName = getJsFriendlyName(octicon.symbol)
const fillRule = result.path.$['fill-rule']
results.push({
jsFriendlyName,
width,
height,
pathData,
fillRule,
})
}
return results
}
generateIconData().then(result => {
console.log(`Writing ${result.length} octicons...`)
const out = fs.createWriteStream(
Path.resolve(__dirname, '../app/src/ui/octicons/octicons.generated.ts'),
{
encoding: 'utf-8',
}
)
out.write('/*\n')
out.write(
' * This file is automatically generated by the generate-octicons tool.\n'
)
out.write(' * Manually changing this file will only lead to sadness.\n')
out.write(' */\n\n')
out.write(
"export type OcticonSymbolType = {readonly w: number, readonly h: number, readonly d: string, readonly fr?: React.SVGAttributes<SVGElement>['fillRule']}\n\n"
)
result.forEach(function (symbol) {
const { jsFriendlyName, pathData, width, height, fillRule } = symbol
out.write(
`export const ${jsFriendlyName}: OcticonSymbolType = {w: ${width}, h: ${height}, d: '${pathData}', fr: ${
fillRule ? `'${fillRule}'` : 'undefined'
}}\n\n`
)
})
out.end()
console.log('Ensuring generated file is formatted correctly...')
const root = Path.dirname(__dirname)
const yarnExecutable = process.platform === 'win32' ? 'yarn.cmd' : 'yarn'
return cp.spawn(yarnExecutable, ['lint:fix'], { cwd: root, stdio: 'inherit' })
})