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