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