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