UNPKG

9.03 kBJavaScriptView Raw
1"use strict";
2
3/**
4 * Provides the class `Source` used to add __tags__ on source files.
5 */
6module.exports = Source;
7
8
9/**
10 * @module source
11 */
12const
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 */
24function 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 */
45Source.prototype.exists = function exists() {
46 return this._absPath ? FS.existsSync( this._absPath ) : false;
47};
48
49Source.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 */
56Source.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 */
65Source.prototype.prj = function () {
66 return this._prj;
67};
68
69/**
70 * @return an instance of Source based on the same project.
71 */
72Source.prototype.create = function ( file ) {
73 return new Source( this._prj, file );
74};
75
76/**
77 * @return Text content of the source.
78 */
79Source.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 */
89Source.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 */
97Source.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 */
180Source.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 */
192Source.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 */
201Source.prototype.getAbsoluteFilePath = function () {
202 return this._absPath;
203};
204
205Source.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 */
223Source.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 */
251Source.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