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