2 # Copyright Simon Lydell 2014, 2015.
4 # This file is part of VimFx.
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.
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.
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/>.
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')
41 LOCALE = 'extension/locale'
42 TEST = 'extension/test'
45 UPDATE_ALL = /\s*UPDATE_ALL$/
47 argv = process.argv.slice(2)
49 test = '--test' in argv or '-t' in argv
50 ifTest = (value) -> if test then [value] else []
53 read = (filepath) -> fs.readFileSync(filepath).toString()
54 template = (data) -> mustache(data, {extension: ''})
56 gulp.task('default', ['push'])
58 gulp.task('clean', (callback) ->
59 rimraf(DEST, callback)
63 gulp.src(['extension/**/!(*.coffee|*.tmpl)', 'COPYING', 'LICENSE'])
64 .pipe(gulp.dest(DEST))
67 gulp.task('node_modules', ->
68 dependencies = (name for name of pkg.dependencies)
69 # Note! When installing or updating node modules, make sure that the following
70 # glob does not include too much or too little!
71 gulp.src("node_modules/+(#{dependencies.join('|')})/\
72 {LICENSE*,{,**/!(test|examples)/}!(*min|*test*|*bench*).js}")
73 .pipe(gulp.dest("#{DEST}/node_modules"))
76 gulp.task('coffee', ->
78 'extension/bootstrap.coffee'
79 'extension/lib/**/*.coffee'
80 ifTest('extension/test/**/*.coffee')...
81 ], {base: 'extension'})
82 .pipe(coffee({bare: true}))
83 .pipe(gulp.dest(DEST))
86 gulp.task('chrome.manifest', ->
87 gulp.src('extension/chrome.manifest.tmpl')
88 .pipe(template({locales: fs.readdirSync(LOCALE).map((locale) -> {locale})}))
89 .pipe(gulp.dest(DEST))
92 gulp.task('install.rdf', ->
93 [[{name: creator}], developers, contributors, translators] =
94 read('PEOPLE.md').trim().replace(/^#.+\n|^\s*-\s*/mg, '').split('\n\n')
95 .map((block) -> block.split('\n').map((name) -> {name}))
97 getDescription = (locale) -> read(join(LOCALE, locale, 'description')).trim()
99 descriptions = fs.readdirSync(LOCALE)
100 .filter((locale) -> locale != BASE_LOCALE)
101 .map((locale) -> {locale, description: getDescription(locale)})
103 gulp.src('extension/install.rdf.tmpl')
105 idSuffix: if '--unlisted' in argv or '-u' in argv then '-unlisted' else ''
107 minVersion: pkg.firefoxVersions.min
108 maxVersion: pkg.firefoxVersions.max
109 creator, developers, contributors, translators
110 defaultDescription: getDescription(BASE_LOCALE)
113 .pipe(gulp.dest(DEST))
116 gulp.task('require-data', ['node_modules'], ->
117 data = JSON.stringify(precompute('.'), null, 2)
118 gulp.src('extension/require-data.js.tmpl')
119 .pipe(template({data}))
120 .pipe(gulp.dest(DEST))
123 gulp.task('tests-list', ->
124 list = JSON.stringify(fs.readdirSync(TEST)
125 .map((name) -> name.match(/^(?!.+-frame\.)(test-.+)\.coffee$/)?[1])
128 gulp.src("#{TEST}/tests-list.js.tmpl", {base: 'extension'})
129 .pipe(template({list}))
130 .pipe(gulp.dest(DEST))
133 gulp.task('tests-list-frame', ->
134 list = JSON.stringify(fs.readdirSync(TEST)
135 .map((name) -> name.match(/^(test-.+-frame)\.coffee$/)?[1])
138 gulp.src("#{TEST}/tests-list-frame.js.tmpl", {base: 'extension'})
139 .pipe(template({list}))
140 .pipe(gulp.dest(DEST))
143 gulp.task('templates', [
147 ifTest('tests-list')...
148 ifTest('tests-list-frame')...
151 gulp.task('build', (callback) ->
154 ['copy', 'node_modules', 'coffee', 'templates'],
159 gulp.task('xpi', ['build'], ->
160 gulp.src("#{DEST}/**/*")
161 .pipe(zip(XPI, {compress: false}))
162 .pipe(gulp.dest(DEST))
165 gulp.task('push', ['xpi'], ->
166 body = fs.readFileSync(join(DEST, XPI))
167 request.post({url: 'http://localhost:8888', body})
171 gulp.src(['extension/**/*.coffee', 'gulpfile.coffee'])
173 .pipe(coffeelint.reporter())
178 'extension/bootstrap.coffee'
179 'extension/lib/!(migrations|legacy).coffee'
184 gulp.task('release', (callback) ->
186 message = "VimFx v#{version}"
187 today = new Date().toISOString()[...10]
189 gulp.src('package.json')
190 gulp.src('CHANGELOG.md')
191 .pipe(header("### #{version} (#{today})\n\n"))
192 .pipe(gulp.dest('.'))
194 .pipe(git.commit(message))
196 git.tag("v#{version}", message, callback)
201 gulp.task('changelog', ->
203 for arg in argv when /^-[1-9]$/.test(arg)
205 entries = read('CHANGELOG.md').split(/^### .+/m)[1..num].join('')
206 process.stdout.write(html(entries))
209 gulp.task('readme', ->
210 process.stdout.write(html(read('README.md')))
213 # Reduce markdown to the small subset of HTML that AMO allows. Note that AMO
214 # converts newlines to `<br>`.
216 return marked(string)
217 .replace(/// <h\d [^>]*> ([^<>]+) </h\d> ///g, '\n\n<b>$1</b>')
218 .replace(///\s* <p> ((?: [^<] | <(?!/p>) )+) </p>///g, (match, text) ->
219 return "\n#{text.replace(/\s*\n\s*/g, ' ')}\n\n"
221 .replace(///<li> ((?: [^<] | <(?!/li>) )+) </li>///g, (match, text) ->
222 return "<li>#{text.replace(/\s*\n\s*/g, ' ')}</li>"
224 .replace(/<br>/g, '\n')
225 .replace(///<(/?)kbd>///g, '<$1code>')
226 .replace(/<img[^>]*>\s*/g, '')
227 .replace(/\n\s*\n/g, '\n\n')
230 gulp.task('faster', ->
231 gulp.src('gulpfile.coffee')
232 .pipe(coffee({bare: true}))
233 .pipe(gulp.dest('.'))
236 gulp.task('sync-locales', ->
237 baseLocale = BASE_LOCALE
239 for arg in argv when arg[...2] == '--'
241 if name[-1..] == '?' then compareLocale = name[...-1] else baseLocale = name
243 results = fs.readdirSync(join(LOCALE, baseLocale))
244 .filter((file) -> path.extname(file) == '.properties')
245 .map(syncLocale.bind(null, baseLocale))
247 if baseLocale == BASE_LOCALE
249 for {fileName, untranslated, total} in results
250 report.push("#{fileName}:")
251 for localeName, strings of untranslated
252 paddedName = "#{localeName}: "[...6]
253 percentage = Math.round((1 - strings.length / total) * 100)
254 if localeName == compareLocale or compareLocale == null
255 report.push(" #{paddedName} #{percentage}%")
256 if localeName == compareLocale
257 report.push(strings.map((string) -> " #{string}")...)
258 process.stdout.write(report.join('\n') + '\n')
261 syncLocale = (baseLocaleName, fileName) ->
262 basePath = join(LOCALE, baseLocaleName, fileName)
263 base = parseLocaleFile(read(basePath))
265 for localeName in fs.readdirSync(LOCALE)
266 localePath = join(LOCALE, localeName, fileName)
267 locale = parseLocaleFile(read(localePath))
268 untranslated[localeName] = []
269 newLocale = base.template.map((line, index) ->
270 if Array.isArray(line)
272 baseValue = base.keys[key]
274 if UPDATE_ALL.test(baseValue) or key not of locale.keys
275 baseValue.replace(UPDATE_ALL, '')
278 result = "#{key}=#{value}"
279 if value == base.keys[key] and value != ''
280 untranslated[localeName].push("#{index + 1}: #{result}")
285 fs.writeFileSync(localePath, newLocale.join(base.newline))
286 delete untranslated[baseLocaleName]
287 return {fileName, untranslated, total: Object.keys(base.keys).length}
289 parseLocaleFile = (fileContents) ->
292 [newline] = fileContents.match(/\r?\n/)
293 for line in fileContents.split(newline)
295 [match, key, value] = line.match(///^ ([^=]+) = (.*) $///) ? []
301 return {keys, template: lines, newline}
303 helpHTMLFile = 'help.html'
304 gulp.task(helpHTMLFile, ->
305 unless fs.existsSync(helpHTMLFile)
306 process.stdout.write("""
307 First enable the “Copy to clipboard” line in help.coffee, show the help
308 dialog and finally dump the clipboard into #{helpHTMLFile}.
311 gulp.src('help.html')
313 file.contents = new Buffer(generateHelpHTML(file.contents.toString()))
315 .pipe(gulp.dest('.'))
318 helpHTMLPrelude = '''
321 <title>VimFx help</title>
324 body > :first-child {min-height: 100vh; width: 100vw;}
326 <link rel=stylesheet href=extension/skin/style.css>
329 generateHelpHTML = (dumpedHTML) ->
330 return helpHTMLPrelude + dumpedHTML
331 .replace(/^<\w+ xmlns="[^"]+"/, '<div')
332 .replace(/\w+>$/, 'div>')
333 .replace(/<(\w+)([^>]*)\/>/g, '<$1$2></$1>')