1 | ;
|
2 |
|
3 | /**
|
4 | * Provides the class `Source` used to add __tags__ on source files.
|
5 | */
|
6 | module.exports = Source;
|
7 |
|
8 |
|
9 | /**
|
10 | * @module source
|
11 | */
|
12 | const
|
13 | FS = require( "fs" ),
|
14 | Path = require( "path" ),
|
15 | Util = require( "./util" ),
|
16 | Fatal = require( "./fatal" );
|
17 |
|
18 | /**
|
19 | * @class Source
|
20 | * @param {Project} prj {@link project~Project}
|
21 | * @param {string} _file path to the source file, relative to the source path.
|
22 | * @see {@link project~Project}
|
23 | */
|
24 | function Source( prj, _file ) {
|
25 | if ( typeof prj === 'string' ) {
|
26 | throw Error( "[Source()] First argument must be an instance of class Project!" );
|
27 | }
|
28 | const
|
29 | srcDir = prj.srcOrLibPath(),
|
30 | file = _file.substr( 0, srcDir.length ) !== srcDir ? _file : _file.substr( srcDir.length );
|
31 |
|
32 | this._prj = prj;
|
33 | this._name = file;
|
34 | this._tmpFile = prj.tmpPath( file );
|
35 | if ( !FS.existsSync( this._tmpFile ) ) {
|
36 | prj.mkdir( Path.dirname( this._tmpFile ) );
|
37 | }
|
38 | this._absPath = prj.srcOrLibPath( file );
|
39 | this._tags = null;
|
40 | }
|
41 |
|
42 | /**
|
43 | * @returns {boolean} - If the source file exists or not.
|
44 | */
|
45 | Source.prototype.exists = function exists() {
|
46 | return this._absPath ? FS.existsSync( this._absPath ) : false;
|
47 | };
|
48 |
|
49 | Source.prototype.clone = function ( newExtension ) {
|
50 | return new Source( this._prj, this.name( newExtension ) );
|
51 | };
|
52 |
|
53 | /**
|
54 | * @return name of the file. It can be `index.html` or `cls/wtag.Button.js`.
|
55 | */
|
56 | Source.prototype.name = function ( newExtension ) {
|
57 | if ( typeof newExtension === 'undefined' ) return this._name;
|
58 | var pos = this._name.lastIndexOf( '.' );
|
59 | return this._name.substr( 0, pos + 1 ) + newExtension;
|
60 | };
|
61 |
|
62 | /**
|
63 | * @return the project ob
|
64 | */
|
65 | Source.prototype.prj = function () {
|
66 | return this._prj;
|
67 | };
|
68 |
|
69 | /**
|
70 | * @return an instance of Source based on the same project.
|
71 | */
|
72 | Source.prototype.create = function ( file ) {
|
73 | return new Source( this._prj, file );
|
74 | };
|
75 |
|
76 | /**
|
77 | * @return Text content of the source.
|
78 | */
|
79 | Source.prototype.read = function () {
|
80 | if ( typeof this._absPath !== 'string' ) {
|
81 | this._prj.fatal( "[Source.read] File not found: \"" + this._name + "\"!" );
|
82 | }
|
83 | return FS.readFileSync( this._absPath ).toString();
|
84 | };
|
85 |
|
86 | /**
|
87 | * @return Text content of the source.
|
88 | */
|
89 | Source.prototype.write = function ( content ) {
|
90 | this._absPath = this._prj.srcPath( this.name() );
|
91 | return FS.writeFileSync( this._absPath, content, { encoding: "utf8" } );
|
92 | };
|
93 |
|
94 | /**
|
95 | * @returns {boolean} `true` if the tags' file exists and is more recent than the source file.
|
96 | */
|
97 | Source.prototype.isUptodate = function () {
|
98 | const
|
99 | path = this.getAbsoluteFilePath(),
|
100 | pathTmp = this._tmpFile,
|
101 | pathJs = Util.replaceExtension( path, ".js" ),
|
102 | pathXjs = Util.replaceExtension( path, ".xjs" ),
|
103 | pathIni = Util.replaceExtension( path, ".ini" ),
|
104 | pathDep = Util.replaceExtension( path, ".dep" ),
|
105 | pathCss = Util.replaceExtension( path, ".css" ),
|
106 | inputs = [ pathJs, pathXjs, pathIni, pathDep, pathCss ],
|
107 | watch = this.tag( 'watch' ) || [];
|
108 | inputs.push( ...watch );
|
109 | return !Util.isNewerThan( inputs, pathTmp );
|
110 |
|
111 | /*
|
112 | if ( !this.existsSync( this._tmpFile ) ) {
|
113 | // If no tmp file exists, we must create it, then this source file is out of date.
|
114 | return false;
|
115 | }
|
116 | const
|
117 | statSrc = FS.statSync( this._absPath ),
|
118 | statTmp = FS.statSync( this._tmpFile );
|
119 | if ( statSrc.mtime < statTmp.mtime ) {
|
120 |
|
121 | if ( !this.name().endsWith( '.js' ) && !this.name().endsWith( '.xjs' ) ) {
|
122 | return true;
|
123 | }
|
124 | // For JS files, we look for INTL file.
|
125 | const
|
126 | pathSrc = this.getAbsoluteFilePath(),
|
127 | pathIni = pathSrc.substr( 0, pathSrc.length - 2 ) + "ini";
|
128 | // Check intl file (*.ini).
|
129 | if ( typeof this.tag( "intl" ) === 'string' && this.tag( "intl" ).length === 0 ) {
|
130 | // If there is no intl file, JS is uptodate.
|
131 | if ( !FS.existsSync( pathIni ) ) return true;
|
132 | return false;
|
133 | } else {
|
134 | if ( FS.existsSync( pathIni ) ) {
|
135 | var statIni = FS.statSync( pathIni );
|
136 | if ( statIni.mtime > statTmp.mtime ) {
|
137 | // Intl file has changed.
|
138 | return false;
|
139 | }
|
140 | } else {
|
141 | // Intl file has been deleted.
|
142 | console.log( "INI file is missing. Creating one: " + pathSrc );
|
143 | FS.writeFileSync( pathIni, "[en]\n\n[fr]\n\n" );
|
144 | return false;
|
145 | }
|
146 | }
|
147 | // For JS files, we look for DEP file.
|
148 | var pathDep = pathSrc.substr( 0, pathSrc.length - 2 ) + "dep";
|
149 | if ( FS.existsSync( pathDep ) ) {
|
150 | var statDep = FS.statSync( pathDep );
|
151 | if ( statDep.mtime > statTmp.mtime ) {
|
152 | // Dep file has changed.
|
153 | return false;
|
154 | }
|
155 | }
|
156 | // Look at watch files. There are files that produce the
|
157 | // `GLOBAL` variable.
|
158 | var watch = this.tag( 'watch' ) || [];
|
159 | var srcWatch, statWatch, k;
|
160 | for ( k = 0; k < watch.length; k++ ) {
|
161 | srcWatch = new Source( this.prj(), watch[ k ] );
|
162 | if ( srcWatch.exists() ) {
|
163 | statWatch = FS.statSync( srcWatch.getAbsoluteFilePath() );
|
164 | if ( statWatch.mtime > statTmp.mtime ) {
|
165 | console.log( watch[ k ] + " => ".cyan + this.name() );
|
166 | return false;
|
167 | }
|
168 | }
|
169 | }
|
170 |
|
171 | return true;
|
172 | }
|
173 | return false;
|
174 | */
|
175 | };
|
176 |
|
177 | /**
|
178 | * @return Last modification time of the source.
|
179 | */
|
180 | Source.prototype.modificationTime = function () {
|
181 | if ( !FS.existsSync( this._tmpFile ) ) {
|
182 | var statSrc = FS.statSync( this._absPath );
|
183 | return statSrc.mtime;
|
184 | }
|
185 | var statTmp = FS.statSync( this._tmpFile );
|
186 | return statTmp.mtime;
|
187 | };
|
188 |
|
189 | /**
|
190 | * Store tags on disk and mark the source file as _uptodate_.
|
191 | */
|
192 | Source.prototype.save = function () {
|
193 | FS.writeFileSync( this._tmpFile, JSON.stringify( this._tags || {} ) );
|
194 | };
|
195 |
|
196 | /**
|
197 | * @return Absolute path of the source file. It can be in the project
|
198 | * `src/` file or in the ToloFrameWork `lib/` path. If it is nowhere,
|
199 | * return _null_.
|
200 | */
|
201 | Source.prototype.getAbsoluteFilePath = function () {
|
202 | return this._absPath;
|
203 | };
|
204 |
|
205 | Source.prototype.getPathRelativeToSource = function ( relPath ) {
|
206 | var dir = Path.dirname( this._absPath );
|
207 | if ( typeof relPath !== 'string' ) {
|
208 | Fatal.fire(
|
209 | 'Argument `relPath` of function `getPathRelativeToSource()` must be a string!\n' +
|
210 | 'But actual value is of type `' + ( typeof relPath ) + '`.'
|
211 | );
|
212 | }
|
213 | return Path.join( dir, relPath );
|
214 | };
|
215 |
|
216 | /**
|
217 | * Get/Set tags.
|
218 | * Don't forget to call method `save()` to store tags on disk and mark the source file has _uptodate_.
|
219 | * @param {string} name name of the tag.
|
220 | * @param value (optional) if defined, the value to set.
|
221 | * @return If `value` is _undefined_, return the tag's value.
|
222 | */
|
223 | Source.prototype.tag = function ( name, value ) {
|
224 | if ( !this._tags ) {
|
225 | if ( FS.existsSync( this._tmpFile ) ) {
|
226 | try {
|
227 | this._tags = JSON.parse( FS.readFileSync( this._tmpFile ).toString() );
|
228 | } catch ( ex ) {
|
229 | this._tags = {};
|
230 | }
|
231 | } else {
|
232 | this._tags = {};
|
233 | }
|
234 | }
|
235 | if ( typeof value === 'undefined' ) {
|
236 | return this._tags[ name ];
|
237 | }
|
238 | if ( value === null ) {
|
239 | delete this._tags[ name ];
|
240 | } else {
|
241 | this._tags[ name ] = value;
|
242 | }
|
243 | };
|
244 |
|
245 | /**
|
246 | * Resources are stored in a directory with the source's name without
|
247 | * extendion. For instance, if the source is "tunnel.css", resources
|
248 | * are stored in directory "tunnel/" in the same folder.
|
249 | * @return Array of resources relative to the source's folder.
|
250 | */
|
251 | Source.prototype.listResources = function () {
|
252 | var dir = this.getAbsoluteFilePath();
|
253 | var pos = dir.lastIndexOf( '.' );
|
254 | if ( pos > -1 ) {
|
255 | // Removing extension.
|
256 | dir = dir.substr( 0, pos );
|
257 | }
|
258 | var root = Path.dirname( dir );
|
259 | var listFiles = function ( path ) {
|
260 | return FS.readdirSync( path ).map(
|
261 | function ( x ) { return Path.join( path, x ); }
|
262 | );
|
263 | };
|
264 | if ( false == FS.existsSync( dir ) ) {
|
265 | return [];
|
266 | }
|
267 | var output = [];
|
268 | var fringe = listFiles( dir );
|
269 | while ( fringe.length > 0 ) {
|
270 | var file = fringe.pop();
|
271 | if ( false == FS.existsSync( file ) ) continue;
|
272 | var stat = FS.statSync( file );
|
273 | if ( stat.isDirectory() ) {
|
274 | fringe = listFiles( file ).concat( fringe );
|
275 | } else {
|
276 | output.push( [ file.substr( root.length + 1 ), file ] );
|
277 | }
|
278 | }
|
279 | return output;
|
280 | }; |
\ | No newline at end of file |