]> git.gir.st - VimFx.git/blob - gulpfile.coffee
Adjustments for extension signing
[VimFx.git] / gulpfile.coffee
1 ###
2 # Copyright Simon Lydell 2014.
3 #
4 # This file is part of VimFx.
5 #
6 # VimFx is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # VimFx is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
18 ###
19
20 fs = require('fs')
21 path = require('path')
22 gulp = require('gulp')
23 coffee = require('gulp-coffee')
24 coffeelint = require('gulp-coffeelint')
25 git = require('gulp-git')
26 header = require('gulp-header')
27 mustache = require('gulp-mustache')
28 sloc = require('gulp-sloc')
29 tap = require('gulp-tap')
30 zip = require('gulp-zip')
31 marked = require('marked')
32 merge = require('merge2')
33 precompute = require('require-precompute')
34 request = require('request')
35 rimraf = require('rimraf')
36 runSequence = require('run-sequence')
37 pkg = require('./package.json')
38
39 DEST = 'build'
40 XPI = 'VimFx.xpi'
41 LOCALE = 'extension/locale'
42 TEST = 'extension/test'
43
44 BASE_LOCALE = 'en-US'
45
46 argv = process.argv.slice(2)
47
48 test = '--test' in argv or '-t' in argv
49 ifTest = (value) -> if test then [value] else []
50
51 {join} = path
52 read = (filepath) -> fs.readFileSync(filepath).toString()
53 template = (data) -> mustache(data, {extension: ''})
54
55 gulp.task('default', ['push'])
56
57 gulp.task('clean', (callback) ->
58 rimraf(DEST, callback)
59 )
60
61 gulp.task('copy', ->
62 gulp.src(['extension/**/!(*.coffee|*.tmpl)', 'COPYING', 'LICENSE'])
63 .pipe(gulp.dest(DEST))
64 )
65
66 gulp.task('node_modules', ->
67 dependencies = (name for name of pkg.dependencies)
68 # Note! When installing or updating node modules, make sure that the following
69 # glob does not include too much or too little!
70 gulp.src("node_modules/+(#{dependencies.join('|')})/\
71 {LICENSE*,{,**/!(test|examples)/}!(*min|*test*|*bench*).js}")
72 .pipe(gulp.dest("#{DEST}/node_modules"))
73 )
74
75 gulp.task('coffee', ->
76 gulp.src([
77 'extension/bootstrap.coffee'
78 'extension/lib/**/*.coffee'
79 ifTest('extension/test/**/*.coffee')...
80 ], {base: 'extension'})
81 .pipe(coffee({bare: true}))
82 .pipe(gulp.dest(DEST))
83 )
84
85 gulp.task('chrome.manifest', ->
86 gulp.src('extension/chrome.manifest.tmpl')
87 .pipe(template({locales: fs.readdirSync(LOCALE).map((locale) -> {locale})}))
88 .pipe(gulp.dest(DEST))
89 )
90
91 gulp.task('install.rdf', ->
92 [[{name: creator}], developers, contributors, translators] =
93 read('PEOPLE.md').trim().replace(/^#.+\n|^\s*-\s*/mg, '').split('\n\n')
94 .map((block) -> block.split('\n').map((name) -> {name}))
95
96 getDescription = (locale) -> read(join(LOCALE, locale, 'description')).trim()
97
98 descriptions = fs.readdirSync(LOCALE)
99 .filter((locale) -> locale != BASE_LOCALE)
100 .map((locale) -> {locale, description: getDescription(locale)})
101
102 gulp.src('extension/install.rdf.tmpl')
103 .pipe(template({
104 idSuffix: if '--unlisted' in argv or '-u' in argv then '-unlisted' else ''
105 version: pkg.version
106 minVersion: pkg.firefoxVersions.min
107 maxVersion: pkg.firefoxVersions.max
108 creator, developers, contributors, translators
109 defaultDescription: getDescription(BASE_LOCALE)
110 descriptions
111 }))
112 .pipe(gulp.dest(DEST))
113 )
114
115 gulp.task('require-data', ['node_modules'], ->
116 data = JSON.stringify(precompute('.'), null, 2)
117 gulp.src('extension/require-data.js.tmpl')
118 .pipe(template({data}))
119 .pipe(gulp.dest(DEST))
120 )
121
122 gulp.task('tests-list', ->
123 list = JSON.stringify(fs.readdirSync(TEST)
124 .map((name) -> name.match(/^(test-.+)\.coffee$/)?[1])
125 .filter(Boolean)
126 )
127 gulp.src("#{TEST}/tests-list.js.tmpl", {base: 'extension'})
128 .pipe(template({list}))
129 .pipe(gulp.dest(DEST))
130 )
131
132 gulp.task('templates', [
133 'chrome.manifest'
134 'install.rdf'
135 'require-data'
136 ifTest('tests-list')...
137 ])
138
139 gulp.task('build', (callback) ->
140 runSequence(
141 'clean',
142 ['copy', 'node_modules', 'coffee', 'templates'],
143 callback
144 )
145 )
146
147 gulp.task('xpi', ['build'], ->
148 gulp.src("#{DEST}/**/*")
149 .pipe(zip(XPI, {compress: false}))
150 .pipe(gulp.dest(DEST))
151 )
152
153 gulp.task('push', ['xpi'], ->
154 body = fs.readFileSync(join(DEST, XPI))
155 request.post({url: 'http://localhost:8888', body})
156 )
157
158 gulp.task('lint', ->
159 gulp.src(['extension/**/*.coffee', 'gulpfile.coffee'])
160 .pipe(coffeelint())
161 .pipe(coffeelint.reporter())
162 )
163
164 gulp.task('sloc', ->
165 gulp.src([
166 'extension/bootstrap.coffee'
167 'extension/lib/!(migrations|legacy).coffee'
168 ])
169 .pipe(sloc())
170 )
171
172 gulp.task('release', (callback) ->
173 {version} = pkg
174 message = "VimFx v#{version}"
175 today = new Date().toISOString()[...10]
176 merge([
177 gulp.src('package.json')
178 gulp.src('CHANGELOG.md')
179 .pipe(header("### #{version} (#{today})\n\n"))
180 .pipe(gulp.dest('.'))
181 ])
182 .pipe(git.commit(message))
183 .on('end', ->
184 git.tag("v#{version}", message, callback)
185 )
186 return
187 )
188
189 gulp.task('changelog', ->
190 num = 1
191 for arg in argv when /^-[1-9]$/.test(arg)
192 num = Number(arg[1])
193 entries = read('CHANGELOG.md').split(/^### .+/m)[1..num].join('')
194 process.stdout.write(html(entries))
195 )
196
197 gulp.task('readme', ->
198 process.stdout.write(html(read('README.md')))
199 )
200
201 # Reduce markdown to the small subset of HTML that AMO allows. Note that AMO
202 # converts newlines to `<br>`.
203 html = (string) ->
204 return marked(string)
205 .replace(/// <h\d [^>]*> ([^<>]+) </h\d> ///g, '\n\n<b>$1</b>')
206 .replace(///\s* <p> ((?: [^<] | <(?!/p>) )+) </p>///g, (match, text) ->
207 return "\n#{text.replace(/\s*\n\s*/g, ' ')}\n\n"
208 )
209 .replace(///<li> ((?: [^<] | <(?!/li>) )+) </li>///g, (match, text) ->
210 return "<li>#{text.replace(/\s*\n\s*/g, ' ')}</li>"
211 )
212 .replace(/<br>/g, '\n')
213 .replace(///<(/?)kbd>///g, '<$1code>')
214 .replace(/<img[^>]*>\s*/g, '')
215 .replace(/\n\s*\n/g, '\n\n')
216 .trim() + '\n'
217
218 gulp.task('faster', ->
219 gulp.src('gulpfile.coffee')
220 .pipe(coffee({bare: true}))
221 .pipe(gulp.dest('.'))
222 )
223
224 gulp.task('sync-locales', ->
225 baseLocale = BASE_LOCALE
226 compareLocale = null
227 for arg in argv when arg[...2] == '--'
228 name = arg[2..]
229 if name[-1..] == '?' then compareLocale = name[...-1] else baseLocale = name
230
231 results = fs.readdirSync(join(LOCALE, baseLocale))
232 .filter((file) -> path.extname(file) == '.properties')
233 .map(syncLocale.bind(null, baseLocale))
234
235 if baseLocale == BASE_LOCALE
236 report = []
237 for {fileName, untranslated, total} in results
238 report.push("#{fileName}:")
239 for localeName, strings of untranslated
240 paddedName = "#{localeName}: "[...6]
241 percentage = Math.round((1 - strings.length / total) * 100)
242 if localeName == compareLocale or compareLocale == null
243 report.push(" #{paddedName} #{percentage}%")
244 if localeName == compareLocale
245 report.push(strings.map((string) -> " #{string}")...)
246 process.stdout.write(report.join('\n') + '\n')
247 )
248
249 syncLocale = (baseLocaleName, fileName) ->
250 basePath = join(LOCALE, baseLocaleName, fileName)
251 base = parseLocaleFile(read(basePath))
252 oldBasePath = "#{basePath}.old"
253 if fs.existsSync(oldBasePath)
254 oldBase = parseLocaleFile(read(oldBasePath))
255 untranslated = {}
256 for localeName in fs.readdirSync(LOCALE) when localeName != baseLocaleName
257 localePath = join(LOCALE, localeName, fileName)
258 locale = parseLocaleFile(read(localePath))
259 untranslated[localeName] = []
260 newLocale = base.template.map((line, index) ->
261 if Array.isArray(line)
262 [key] = line
263 oldValue = oldBase?.keys[key]
264 value =
265 if (oldValue? and oldValue != base.keys[key]) or
266 key not of locale.keys
267 base.keys[key]
268 else
269 locale.keys[key]
270 result = "#{key}=#{value}"
271 if value == base.keys[key] and value != ''
272 untranslated[localeName].push("#{index + 1}: #{result}")
273 return result
274 else
275 return line
276 )
277 fs.writeFileSync(localePath, newLocale.join(base.newline))
278 return {fileName, untranslated, total: Object.keys(base.keys).length}
279
280 parseLocaleFile = (fileContents) ->
281 keys = {}
282 lines = []
283 [newline] = fileContents.match(/\r?\n/)
284 for line in fileContents.split(newline)
285 line = line.trim()
286 [match, key, value] = line.match(///^ ([^=]+) = (.*) $///) ? []
287 if match
288 keys[key] = value
289 lines.push([key])
290 else
291 lines.push(line)
292 return {keys, template: lines, newline}
293
294 helpHTMLFile = 'help.html'
295 gulp.task(helpHTMLFile, ->
296 unless fs.existsSync(helpHTMLFile)
297 process.stdout.write("""
298 First enable the “Copy to clipboard” line in help.coffee, show the help
299 dialog and finally dump the clipboard into #{helpHTMLFile}.
300 """)
301 return
302 gulp.src('help.html')
303 .pipe(tap((file) ->
304 file.contents = new Buffer(generateHelpHTML(file.contents.toString()))
305 ))
306 .pipe(gulp.dest('.'))
307 )
308
309 helpHTMLPrelude = '''
310 <!doctype html>
311 <meta charset=utf-8>
312 <title>VimFx help</title>
313 <style>
314 * {margin: 0;}
315 body > :first-child {min-height: 100vh;}
316 </style>
317 <link rel=stylesheet href=extension/skin/style.css>
318 '''
319
320 generateHelpHTML = (dumpedHTML) ->
321 return helpHTMLPrelude + dumpedHTML
322 .replace(/^<\w+ xmlns="[^"]+"/, '<div')
323 .replace(/\w+>$/, 'div>')
324 .replace(/<(\w+)([^>]*)\/>/g, '<$1$2></$1>')
Imprint / Impressum