File : AppLoader.js

1
/*
2
Copyright - 2021 - wwwouaiebe - Contact: https://www.ouaie.be/
3
4
This  program is free software;
5
you can redistribute it and/or modify it under the terms of the
6
GNU General Public License as published by the Free Software Foundation;
7
either version 3 of the License, or any later version.
8
9
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU General Public License for more details.
13
14
You should have received a copy of the GNU General Public License
15
along with this program; if not, write to the Free Software
16
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
*/
18
/*
19
Changes:
20
    - v1.0.0:
21
        - created
22
    - v1.1.0:
23
        - Issue ♯2 : Add a version number...
24
    - v1.1.0:
25
        - Issue ♯3 : String.substr ( ) is deprecated... Replace...
26
    - v1.2.0:
27
        - Issue ♯4 : Add an option to avoid the HTML files generation
28
Doc reviewed 20211111
29
*/
30
/* ------------------------------------------------------------------------------------------------------------------------- */
31
32
import fs from 'fs';
33
import process from 'process';
34
import childProcess from 'child_process';
35
36
import DocBuilder from './DocBuilder.js';
37
import theConfig from './Config.js';
38
39
/* ------------------------------------------------------------------------------------------------------------------------- */
40
/**
41
Start the app:
42
- read and validate the arguments
43
- set the config
44
- create the source file list
45
- remove the old documentation if any
46
*/
47
/* ------------------------------------------------------------------------------------------------------------------------- */
48
49
class AppLoader {
50
51
    /**
52
    The source files names, included the path since theConfig.srcDir
53
    @type {Array.<String>}
54
    */
55
56
    #sourceFileNames;
57
58
    /**
59
    A const to use when exit the app due to a bad parameter
60
    @type {Number}
61
    */
62
63
    // eslint-disable-next-line no-magic-numbers
64
    static get #EXIT_BAD_PARAMETER ( ) { return 9; }
65
66
    /**
67
    The version number
68
    @type {String}
69
    */
70
71
    static get #version ( ) { return 'v1.3.11'; }
72
73
    /**
74
    The constructor
75
    */
76
77
    constructor ( ) {
78
        Object.freeze ( this );
79
        this.#sourceFileNames = [];
80
    }
81
82
    /**
83
    Read **recursively** the contains of a directory and store all the js files found in the #sourceFileNames property
84
    @param {String} dir The directory to read. It's a relative path, starting at theConfig.srcDir ( the path
85
    given in the --src parameter )
86
    */
87
88
    #readDir ( dir ) {
89
90
        // Searching all files and directories present in the directory
91
        const fileNames = fs.readdirSync ( theConfig.srcDir + dir );
92
93
        // Loop on the results
94
        fileNames.forEach (
95
            fileName => {
96
97
                // Searching the stat of the file/directory
98
                const lstat = fs.lstatSync ( theConfig.srcDir + dir + fileName );
99
100
                if ( lstat.isDirectory ( ) ) {
101
102
                    // It's a directory. Reading this recursively
103
                    this.#readDir ( dir + fileName + '/' );
104
                }
105
                else if ( lstat.isFile ( ) ) {
106
107
                    // it's a file. Adding to the files list with the relative path, if the extension is 'js'
108
                    if ( 'js' === fileName.split ( '.' ).reverse ( )[ 0 ] ) {
109
                        this.#sourceFileNames.push ( dir + fileName );
110
                    }
111
                }
112
            }
113
        );
114
    }
115
116
    /**
117
    Clean the previously created files, to avoid deprecated files in the documentation.
118
    */
119
120
    #cleanOldFiles ( ) {
121
        try {
122
123
            // Removing the complete documentation directory
124
            fs.rmSync (
125
                theConfig.destDir,
126
                { recursive : true, force : true },
127
                err => {
128
                    if ( err ) {
129
                        throw err;
130
                    }
131
                }
132
            );
133
134
            // and then recreating
135
            fs.mkdirSync ( theConfig.destDir );
136
        }
137
        catch {
138
139
            // Sometime the cleaning fails due to opened files
140
            console.error ( `\x1b[31mNot possible to clean the ${theConfig.destDir} folder\x1b[0m` );
141
        }
142
    }
143
144
    /**
145
    Show the help on the screen
146
    */
147
148
    #showHelp ( ) {
149
        console.error ( '\n\t\x1b[36m--help\x1b[0m : this help\n' );
150
        console.error ( '\t\x1b[36m--version\x1b[0m : the version number\n' );
151
        console.error ( '\t\x1b[36m--src\x1b[0m : the path to the directory where the sources are located\n' );
152
        console.error (
153
            '\t\x1b[36m--dest\x1b[0m : the path to the directory where' +
154
            ' the documentation have to be generated\n'
155
        );
156
        console.error ( '\t\x1b[36m--validate\x1b[0m : when present, the documentation is validated\n' );
157
        console.error (
158
            '\t\x1b[36m--launch\x1b[0m : when present, the documentation will' +
159
            ' be opened in the browser at the end of the process\n'
160
        );
161
        console.error (
162
            '\t\x1b[36m--noSourcesColor\x1b[0m : when present, the sources files will' +
163
            ' not have colors for JS keywords and links for types\n'
164
        );
165
        process.exit ( 0 );
166
    }
167
168
    /**
169
    Validate a path:
170
    - Verify that the path exists on the computer
171
    - verify that the path is a directory
172
    - complete the path with a \
173
    @param {String} path The path to validate
174
    */
175
176
    #validatePath ( path ) {
177
        let returnPath = path;
178
        if ( '' === returnPath ) {
179
            console.error ( 'Invalid or missing \x1b[31m--src or dest\x1b[0m parameter' );
180
            process.exit ( AppLoader.#EXIT_BAD_PARAMETER );
181
        }
182
        let pathSeparator = null;
183
        try {
184
            returnPath = fs.realpathSync ( path );
185
186
            // path.sep seems not working...
187
            pathSeparator = -1 === returnPath.indexOf ( '\\' ) ? '/' : '\\';
188
            const lstat = fs.lstatSync ( returnPath );
189
            if ( lstat.isFile ( ) ) {
190
                returnPath = returnPath.substring ( 0, returnPath.lastIndexOf ( pathSeparator ) );
191
            }
192
        }
193
        catch {
194
            console.error ( 'Invalid path for the --src or --dest parameter \x1b[31m%s\x1b[0m', returnPath );
195
            process.exit ( AppLoader.#EXIT_BAD_PARAMETER );
196
        }
197
        returnPath += pathSeparator;
198
        return returnPath;
199
    }
200
201
    /**
202
    Complete theConfig object from the app parameters
203
    @param {?Object} options The options for the app
204
    */
205
206
    #createConfig ( options ) {
207
208
        if ( options ) {
209
            theConfig.srcDir = options.src;
210
            theConfig.destDir = options.dest;
211
            theConfig.appDir = process.cwd ( ) + '/node_modules/essimpledoc/src';
212
            if ( options.launch ) {
213
                theConfig.launch = true;
214
            }
215
            if ( options.noSourcesColor ) {
216
                theConfig.noSourcesColor = true;
217
            }
218
            if ( options.validate ) {
219
                theConfig.validate = true;
220
            }
221
            if ( options.noFiles ) {
222
                theConfig.noFiles = true;
223
            }
224
        }
225
        else {
226
            process.argv.forEach (
227
                arg => {
228
                    const argContent = arg.split ( '=' );
229
                    switch ( argContent [ 0 ] ) {
230
                    case '--src' :
231
                        theConfig.srcDir = argContent [ 1 ] || theConfig.srcDir;
232
                        break;
233
                    case '--dest' :
234
                        theConfig.destDir = argContent [ 1 ] || theConfig.destDir;
235
                        break;
236
                    case '--validate' :
237
                        theConfig.validate = true;
238
                        break;
239
                    case '--noFiles' :
240
                        theConfig.noFiles = true;
241
                        break;
242
                    case '--launch' :
243
                        theConfig.launch = true;
244
                        break;
245
                    case '--noSourcesColor' :
246
                        theConfig.noSourcesColor = true;
247
                        break;
248
                    case '--help' :
249
                        this.#showHelp ( );
250
                        break;
251
                    case '--version' :
252
                        console.error ( `\n\t\x1b[36mVersion : ${AppLoader.#version}\x1b[0m\n` );
253
                        process.exit ( 0 );
254
                        break;
255
                    default :
256
                        break;
257
                    }
258
                }
259
            );
260
            theConfig.appDir = process.argv [ 1 ];
261
        }
262
        theConfig.srcDir = this.#validatePath ( theConfig.srcDir );
263
        theConfig.destDir = this.#validatePath ( theConfig.destDir );
264
        theConfig.appDir = this.#validatePath ( theConfig.appDir );
265
266
        // the config is now frozen
267
        Object.freeze ( theConfig );
268
    }
269
270
    /**
271
    Load the app, searching all the needed infos to run the app correctly
272
    @param {?Object} options The options for the app
273
    */
274
275
    loadApp ( options ) {
276
277
        // start time
278
        const startTime = process.hrtime.bigint ( );
279
280
        // config
281
        this.#createConfig ( options );
282
283
        // console.clear ( );
284
        console.error ( `\nStarting ESSimpleDoc ${AppLoader.#version}...` );
285
286
        // source files list
287
        this.#readDir ( '' );
288
289
        if ( ! theConfig.noFiles ) {
290
291
            // Cleaning old files
292
            this.#cleanOldFiles ( );
293
294
            // copy the css file in the documentation directory
295
            fs.copyFileSync ( theConfig.appDir + '../src/ESSimpleDoc.css', theConfig.destDir + 'ESSimpleDoc.css' );
296
        }
297
298
        // starting the build
299
        new DocBuilder ( ).buildFiles ( this.#sourceFileNames );
300
301
        // end of the process
302
        const deltaTime = process.hrtime.bigint ( ) - startTime;
303
304
        /* eslint-disable-next-line no-magic-numbers */
305
        const execTime = String ( deltaTime / 1000000000n ) + '.' + String ( deltaTime % 1000000000n ).substring ( 0, 3 );
306
        console.error ( `\nDocumentation generated in ${execTime} seconds in the folder \x1b[36m${theConfig.destDir}\x1b[0m` );
307
        if ( theConfig.launch ) {
308
            console.error ( '\n\t... launching in the browser...\n' );
309
            childProcess.exec ( theConfig.destDir + 'index.html' );
310
        }
311
    }
312
}
313
314
export default AppLoader;
315
316
/* --- End of file --------------------------------------------------------------------------------------------------------- */
317