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