Bug 33054: Fix unit tests
[koha.git] / gulpfile.js
1 /* eslint-env node */
2 /* eslint no-console:"off" */
3
4 const { dest, parallel, series, src, watch } = require('gulp');
5
6 const child_process = require('child_process');
7 const fs = require('fs');
8 const os = require('os');
9 const path = require('path');
10 const util = require('util');
11
12 const sass = require('gulp-sass')(require('sass'));
13 const rtlcss = require('gulp-rtlcss');
14 const sourcemaps = require('gulp-sourcemaps');
15 const autoprefixer = require('gulp-autoprefixer');
16 const concatPo = require('gulp-concat-po');
17 const exec = require('gulp-exec');
18 const merge = require('merge-stream');
19 const through2 = require('through2');
20 const Vinyl = require('vinyl');
21 const args = require('minimist')(process.argv.slice(2));
22 const rename = require('gulp-rename');
23
24 const STAFF_JS_BASE = "koha-tmpl/intranet-tmpl/prog/js";
25 const STAFF_CSS_BASE = "koha-tmpl/intranet-tmpl/prog/css";
26 const OPAC_JS_BASE = "koha-tmpl/opac-tmpl/bootstrap/js";
27 const OPAC_CSS_BASE = "koha-tmpl/opac-tmpl/bootstrap/css";
28
29 if (args.view == "opac") {
30     var css_base = OPAC_CSS_BASE;
31     var js_base = OPAC_JS_BASE;
32 } else {
33     var css_base = STAFF_CSS_BASE;
34     var js_base = STAFF_JS_BASE;
35 }
36
37 var sassOptions = {
38     includePaths: [
39         __dirname + '/node_modules',
40         __dirname + '/../node_modules'
41     ]
42 }
43
44 // CSS processing for development
45 function css() {
46     var stream = src(css_base + "/src/**/*.scss")
47         .pipe(sourcemaps.init())
48         .pipe(sass(sassOptions).on('error', sass.logError))
49         .pipe(autoprefixer())
50         .pipe(dest(css_base));
51
52     if (args.view == "opac") {
53         stream = stream
54             .pipe(rtlcss())
55             .pipe(rename({
56                 suffix: '-rtl'
57             })) // Append "-rtl" to the filename.
58             .pipe(dest(css_base));
59     }
60
61     stream = stream.pipe(sourcemaps.write('./maps'))
62         .pipe(dest(css_base));
63
64     return stream;
65
66 }
67 // CSS processing for production
68 function build(css_base) {
69     css_base = css_base || CSS_BASE;
70     sassOptions.outputStyle = "compressed";
71     var stream = src(css_base + "/src/**/*.scss")
72         .pipe(sass(sassOptions).on('error', sass.logError))
73         .pipe(autoprefixer())
74         .pipe(dest(css_base));
75
76     if( args.view == "opac" ){
77         stream = stream.pipe(rtlcss())
78         .pipe(rename({
79             suffix: '-rtl'
80         })) // Append "-rtl" to the filename.
81         .pipe(dest(css_base));
82     }
83
84     return stream;
85 }
86
87 const poTasks = {
88     'marc-MARC21': {
89         extract: po_extract_marc_marc21,
90         create: po_create_marc_marc21,
91         update: po_update_marc_marc21,
92     },
93     'marc-UNIMARC': {
94         extract: po_extract_marc_unimarc,
95         create: po_create_marc_unimarc,
96         update: po_update_marc_unimarc,
97     },
98     'staff-prog': {
99         extract: po_extract_staff,
100         create: po_create_staff,
101         update: po_update_staff,
102     },
103     'opac-bootstrap': {
104         extract: po_extract_opac,
105         create: po_create_opac,
106         update: po_update_opac,
107     },
108     'pref': {
109         extract: po_extract_pref,
110         create: po_create_pref,
111         update: po_update_pref,
112     },
113     'messages': {
114         extract: po_extract_messages,
115         create: po_create_messages,
116         update: po_update_messages,
117     },
118     'messages-js': {
119         extract: po_extract_messages_js,
120         create: po_create_messages_js,
121         update: po_update_messages_js,
122     },
123     'installer': {
124         extract: po_extract_installer,
125         create: po_create_installer,
126         update: po_update_installer,
127     },
128     'installer-MARC21': {
129         extract: po_extract_installer_marc21,
130         create: po_create_installer_marc21,
131         update: po_update_installer_marc21,
132     },
133     'installer-UNIMARC': {
134         extract: po_extract_installer_unimarc,
135         create: po_create_installer_unimarc,
136         update: po_update_installer_unimarc,
137     },
138 };
139
140 const poTypes = Object.keys(poTasks);
141
142 function po_extract_marc (type) {
143     return src(`koha-tmpl/*-tmpl/*/en/**/*${type}*`, { read: false, nocase: true })
144         .pipe(xgettext('misc/translator/xgettext.pl --charset=UTF-8 -s', `Koha-marc-${type}.pot`))
145         .pipe(dest('misc/translator'))
146 }
147
148 function po_extract_marc_marc21 ()  { return po_extract_marc('MARC21') }
149 function po_extract_marc_unimarc () { return po_extract_marc('UNIMARC') }
150
151 function po_extract_staff () {
152     const globs = [
153         'koha-tmpl/intranet-tmpl/prog/en/**/*.tt',
154         'koha-tmpl/intranet-tmpl/prog/en/**/*.inc',
155         'koha-tmpl/intranet-tmpl/prog/en/xslt/*.xsl',
156         '!koha-tmpl/intranet-tmpl/prog/en/**/*MARC21*',
157         '!koha-tmpl/intranet-tmpl/prog/en/**/*UNIMARC*',
158         '!koha-tmpl/intranet-tmpl/prog/en/**/*marc21*',
159         '!koha-tmpl/intranet-tmpl/prog/en/**/*unimarc*',
160     ];
161
162     return src(globs, { read: false, nocase: true })
163         .pipe(xgettext('misc/translator/xgettext.pl --charset=UTF-8 -s', 'Koha-staff-prog.pot'))
164         .pipe(dest('misc/translator'))
165 }
166
167 function po_extract_opac () {
168     const globs = [
169         'koha-tmpl/opac-tmpl/bootstrap/en/**/*.tt',
170         'koha-tmpl/opac-tmpl/bootstrap/en/**/*.inc',
171         'koha-tmpl/opac-tmpl/bootstrap/en/xslt/*.xsl',
172         '!koha-tmpl/opac-tmpl/bootstrap/en/**/*MARC21*',
173         '!koha-tmpl/opac-tmpl/bootstrap/en/**/*UNIMARC*',
174         '!koha-tmpl/opac-tmpl/bootstrap/en/**/*marc21*',
175         '!koha-tmpl/opac-tmpl/bootstrap/en/**/*unimarc*',
176     ];
177
178     return src(globs, { read: false, nocase: true })
179         .pipe(xgettext('misc/translator/xgettext.pl --charset=UTF-8 -s', 'Koha-opac-bootstrap.pot'))
180         .pipe(dest('misc/translator'))
181 }
182
183 const xgettext_options = '--from-code=UTF-8 --package-name Koha '
184     + '--package-version= -k -k__ -k__x -k__n:1,2 -k__nx:1,2 -k__xn:1,2 '
185     + '-k__p:1c,2 -k__px:1c,2 -k__np:1c,2,3 -k__npx:1c,2,3 -kN__ '
186     + '-kN__n:1,2 -kN__p:1c,2 -kN__np:1c,2,3 '
187     + '-k -k$__ -k$__x -k$__n:1,2 -k$__nx:1,2 -k$__xn:1,2 '
188     + '--force-po';
189
190 function po_extract_messages_js () {
191     const globs = [
192         'koha-tmpl/intranet-tmpl/prog/js/vue/**/*.vue',
193         'koha-tmpl/intranet-tmpl/prog/js/**/*.js',
194         'koha-tmpl/opac-tmpl/bootstrap/js/**/*.js',
195     ];
196
197     return src(globs, { read: false, nocase: true })
198         .pipe(xgettext(`xgettext -L JavaScript ${xgettext_options}`, 'Koha-messages-js.pot'))
199         .pipe(dest('misc/translator'))
200 }
201
202 function po_extract_messages () {
203     const perlStream = src(['**/*.pl', '**/*.pm'], { read: false, nocase: true })
204         .pipe(xgettext(`xgettext -L Perl ${xgettext_options}`, 'Koha-perl.pot'))
205
206     const ttStream = src([
207             'koha-tmpl/intranet-tmpl/prog/en/**/*.tt',
208             'koha-tmpl/intranet-tmpl/prog/en/**/*.inc',
209             'koha-tmpl/opac-tmpl/bootstrap/en/**/*.tt',
210             'koha-tmpl/opac-tmpl/bootstrap/en/**/*.inc',
211         ], { read: false, nocase: true })
212         .pipe(xgettext('misc/translator/xgettext-tt2 --from-code=UTF-8', 'Koha-tt.pot'))
213
214     const headers = {
215         'Project-Id-Version': 'Koha',
216         'Content-Type': 'text/plain; charset=UTF-8',
217     };
218
219     return merge(perlStream, ttStream)
220         .pipe(concatPo('Koha-messages.pot', { headers }))
221         .pipe(dest('misc/translator'))
222 }
223
224 function po_extract_pref () {
225     return src('koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/*.pref', { read: false })
226         .pipe(xgettext('misc/translator/xgettext-pref', 'Koha-pref.pot'))
227         .pipe(dest('misc/translator'))
228 }
229
230 function po_extract_installer () {
231     const globs = [
232         'installer/data/mysql/en/mandatory/*.yml',
233         'installer/data/mysql/en/optional/*.yml',
234     ];
235
236     return src(globs, { read: false, nocase: true })
237         .pipe(xgettext('misc/translator/xgettext-installer', 'Koha-installer.pot'))
238         .pipe(dest('misc/translator'))
239 }
240
241 function po_extract_installer_marc (type) {
242     const globs = `installer/data/mysql/en/marcflavour/${type}/**/*.yml`;
243
244     return src(globs, { read: false, nocase: true })
245         .pipe(xgettext('misc/translator/xgettext-installer', `Koha-installer-${type}.pot`))
246         .pipe(dest('misc/translator'))
247 }
248
249 function po_extract_installer_marc21 ()  { return po_extract_installer_marc('MARC21') }
250
251 function po_extract_installer_unimarc ()  { return po_extract_installer_marc('UNIMARC') }
252
253 function po_create_type (type) {
254     const access = util.promisify(fs.access);
255     const exec = util.promisify(child_process.exec);
256
257     const languages = getLanguages();
258     const promises = [];
259     for (const language of languages) {
260         const locale = language.split('-').filter(s => s.length !== 4).join('_');
261         const po = `misc/translator/po/${language}-${type}.po`;
262         const pot = `misc/translator/Koha-${type}.pot`;
263
264         const promise = access(po)
265             .catch(() => exec(`msginit -o ${po} -i ${pot} -l ${locale} --no-translator`))
266         promises.push(promise);
267     }
268
269     return Promise.all(promises);
270 }
271
272 function po_create_marc_marc21 ()       { return po_create_type('marc-MARC21') }
273 function po_create_marc_unimarc ()      { return po_create_type('marc-UNIMARC') }
274 function po_create_staff ()             { return po_create_type('staff-prog') }
275 function po_create_opac ()              { return po_create_type('opac-bootstrap') }
276 function po_create_pref ()              { return po_create_type('pref') }
277 function po_create_messages ()          { return po_create_type('messages') }
278 function po_create_messages_js ()       { return po_create_type('messages-js') }
279 function po_create_installer ()         { return po_create_type('installer') }
280 function po_create_installer_marc21 ()  { return po_create_type('installer-MARC21') }
281 function po_create_installer_unimarc () { return po_create_type('installer-UNIMARC') }
282
283 function po_update_type (type) {
284     const msgmerge_opts = '--backup=off --quiet --sort-output --update';
285     const cmd = `msgmerge ${msgmerge_opts} <%= file.path %> misc/translator/Koha-${type}.pot`;
286     const languages = getLanguages();
287     const globs = languages.map(language => `misc/translator/po/${language}-${type}.po`);
288
289     return src(globs)
290         .pipe(exec(cmd, { continueOnError: true }))
291         .pipe(exec.reporter({ err: false, stdout: false }))
292 }
293
294 function po_update_marc_marc21 ()       { return po_update_type('marc-MARC21') }
295 function po_update_marc_unimarc ()      { return po_update_type('marc-UNIMARC') }
296 function po_update_staff ()             { return po_update_type('staff-prog') }
297 function po_update_opac ()              { return po_update_type('opac-bootstrap') }
298 function po_update_pref ()              { return po_update_type('pref') }
299 function po_update_messages ()          { return po_update_type('messages') }
300 function po_update_messages_js ()       { return po_update_type('messages-js') }
301 function po_update_installer ()         { return po_update_type('installer') }
302 function po_update_installer_marc21 ()  { return po_update_type('installer-MARC21') }
303 function po_update_installer_unimarc () { return po_update_type('installer-UNIMARC') }
304
305 /**
306  * Gulp plugin that executes xgettext-like command `cmd` on all files given as
307  * input, and then outputs the result as a POT file named `filename`.
308  * `cmd` should accept -o and -f options
309  */
310 function xgettext (cmd, filename) {
311     const filenames = [];
312
313     function transform (file, encoding, callback) {
314         filenames.push(path.relative(file.cwd, file.path));
315         callback();
316     }
317
318     function flush (callback) {
319         fs.mkdtemp(path.join(os.tmpdir(), 'koha-'), (err, folder) => {
320             const outputFilename = path.join(folder, filename);
321             const filesFilename = path.join(folder, 'files');
322             fs.writeFile(filesFilename, filenames.join(os.EOL), err => {
323                 if (err) return callback(err);
324
325                 const command = `${cmd} -o ${outputFilename} -f ${filesFilename}`;
326                 child_process.exec(command, err => {
327                     if (err) return callback(err);
328
329                     fs.readFile(outputFilename, (err, data) => {
330                         if (err) return callback(err);
331
332                         const file = new Vinyl();
333                         file.path = path.join(file.base, filename);
334                         file.contents = data;
335                         callback(null, file);
336                     });
337                 });
338             });
339         })
340     }
341
342     return through2.obj(transform, flush);
343 }
344
345 /**
346  * Return languages selected for PO-related tasks
347  *
348  * This can be either languages given on command-line with --lang option, or
349  * all the languages found in misc/translator/po otherwise
350  */
351 function getLanguages () {
352     if (Array.isArray(args.lang)) {
353         return args.lang;
354     }
355
356     if (args.lang) {
357         return [args.lang];
358     }
359
360     const filenames = fs.readdirSync('misc/translator/po')
361         .filter(filename => filename.endsWith('.po'))
362         .filter(filename => !filename.startsWith('.'))
363
364     const re = new RegExp('-(' + poTypes.join('|') + ')\.po$');
365     languages = filenames.map(filename => filename.replace(re, ''))
366
367     return Array.from(new Set(languages));
368 }
369
370 exports.build = build;
371 exports.css = css;
372
373 exports['po:create'] = parallel(...poTypes.map(type => series(poTasks[type].extract, poTasks[type].create)));
374 exports['po:update'] = parallel(...poTypes.map(type => series(poTasks[type].extract, poTasks[type].update)));
375 exports['po:extract'] = parallel(...poTypes.map(type => poTasks[type].extract));
376
377 exports.default = function () {
378     watch(css_base + "/src/**/*.scss", series('css'));
379 }