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