You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

657 lines
19 KiB

  1. // Generated by CoffeeScript 1.6.3
  2. var Backbone, FileModel, Iconv, Model, TaskGroup, balUtil, docpadUtil, extendr, extractOptsAndCallback, jschardet, mime, pathUtil, safefs, typeChecker, _ref, _ref1,
  3. __hasProp = {}.hasOwnProperty,
  4. __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
  5. __slice = [].slice;
  6. pathUtil = require('path');
  7. balUtil = require('bal-util');
  8. typeChecker = require('typechecker');
  9. TaskGroup = require('taskgroup').TaskGroup;
  10. safefs = require('safefs');
  11. mime = require('mime');
  12. extendr = require('extendr');
  13. extractOptsAndCallback = require('extract-opts').extractOptsAndCallback;
  14. jschardet = null;
  15. Iconv = null;
  16. _ref = require('../base'), Backbone = _ref.Backbone, Model = _ref.Model;
  17. docpadUtil = require('../util');
  18. FileModel = (function(_super) {
  19. __extends(FileModel, _super);
  20. function FileModel() {
  21. _ref1 = FileModel.__super__.constructor.apply(this, arguments);
  22. return _ref1;
  23. }
  24. FileModel.prototype.klass = FileModel;
  25. FileModel.prototype.type = 'file';
  26. FileModel.prototype.outDirPath = null;
  27. FileModel.prototype.detectEncoding = false;
  28. FileModel.prototype.stat = null;
  29. FileModel.prototype.data = null;
  30. FileModel.prototype.buffer = null;
  31. FileModel.prototype.meta = null;
  32. FileModel.prototype.getOpts = function() {
  33. return {
  34. outDirPath: this.outDirPath,
  35. detectEncoding: this.detectEncoding,
  36. stat: this.stat,
  37. data: this.data,
  38. buffer: this.buffer,
  39. meta: this.meta
  40. };
  41. };
  42. FileModel.prototype.clone = function() {
  43. var attrs, instance, opts;
  44. opts = this.getOpts();
  45. attrs = this.getAttributes();
  46. instance = new this.klass(attrs, opts);
  47. instance._events = extendr.deepExtend(this._events);
  48. return instance;
  49. };
  50. FileModel.prototype.defaults = {
  51. id: null,
  52. basename: null,
  53. outBasename: null,
  54. extension: null,
  55. outExtension: null,
  56. extensions: null,
  57. filename: null,
  58. fullPath: null,
  59. fullDirPath: null,
  60. outPath: null,
  61. outDirPath: null,
  62. outFilename: null,
  63. relativePath: null,
  64. relativeOutPath: null,
  65. relativeDirPath: null,
  66. relativeOutDirPath: null,
  67. relativeBase: null,
  68. releativeOutBase: null,
  69. contentType: null,
  70. outContentType: null,
  71. ctime: null,
  72. mtime: null,
  73. exists: null,
  74. encoding: null,
  75. source: null,
  76. content: null,
  77. title: null,
  78. name: null,
  79. date: null,
  80. slug: null,
  81. url: null,
  82. urls: null,
  83. ignored: false,
  84. standalone: false
  85. };
  86. FileModel.prototype.setData = function(data) {
  87. this.data = data;
  88. return this;
  89. };
  90. FileModel.prototype.getData = function() {
  91. return this.data;
  92. };
  93. FileModel.prototype.setBuffer = function(buffer) {
  94. this.buffer = buffer;
  95. return this;
  96. };
  97. FileModel.prototype.getBuffer = function() {
  98. return this.buffer;
  99. };
  100. FileModel.prototype.setStat = function(stat) {
  101. this.stat = stat;
  102. this.set({
  103. ctime: new Date(stat.ctime),
  104. mtime: new Date(stat.mtime)
  105. });
  106. return this;
  107. };
  108. FileModel.prototype.getStat = function() {
  109. return this.stat;
  110. };
  111. FileModel.prototype.getAttributes = function() {
  112. var attrs;
  113. attrs = this.toJSON();
  114. attrs = extendr.dereference(attrs);
  115. return attrs;
  116. };
  117. FileModel.prototype.toJSON = function() {
  118. var data;
  119. data = FileModel.__super__.toJSON.apply(this, arguments);
  120. data.meta = this.getMeta().toJSON();
  121. return data;
  122. };
  123. FileModel.prototype.getMeta = function() {
  124. var args, _ref2;
  125. args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
  126. if (this.meta === null) {
  127. this.meta = new Model();
  128. }
  129. if (args.length) {
  130. return (_ref2 = this.meta).get.apply(_ref2, args);
  131. } else {
  132. return this.meta;
  133. }
  134. };
  135. FileModel.prototype.setMeta = function(attrs) {
  136. var _ref2;
  137. attrs = (_ref2 = typeof attrs.toJSON === "function" ? attrs.toJSON() : void 0) != null ? _ref2 : attrs;
  138. this.getMeta().set(attrs);
  139. this.set(attrs);
  140. return this;
  141. };
  142. FileModel.prototype.setMetaDefaults = function(defaults) {
  143. this.getMeta().setDefaults(defaults);
  144. this.setDefaults(defaults);
  145. return this;
  146. };
  147. FileModel.prototype.getFilename = function(_arg) {
  148. var filePath, filename, fullPath, relativePath;
  149. filename = _arg.filename, fullPath = _arg.fullPath, relativePath = _arg.relativePath;
  150. filename || (filename = this.get('filename'));
  151. if (!filename) {
  152. filePath = this.get('fullPath') || this.get('relativePath');
  153. if (filePath) {
  154. filename = pathUtil.basename(filePath);
  155. }
  156. }
  157. return filename || null;
  158. };
  159. FileModel.prototype.getExtensions = function(_arg) {
  160. var extensions, filename;
  161. extensions = _arg.extensions, filename = _arg.filename;
  162. extensions || (extensions = this.get('extensions') || null);
  163. if ((extensions || []).length === 0) {
  164. filename = this.getFilename({
  165. filename: filename
  166. });
  167. if (filename) {
  168. extensions = docpadUtil.getExtensions(filename);
  169. }
  170. }
  171. return extensions || null;
  172. };
  173. FileModel.prototype.getContent = function() {
  174. return this.get('content') || this.getBuffer();
  175. };
  176. FileModel.prototype.getOutContent = function() {
  177. return this.getContent();
  178. };
  179. FileModel.prototype.isText = function() {
  180. return this.get('encoding') !== 'binary';
  181. };
  182. FileModel.prototype.isBinary = function() {
  183. return this.get('encoding') === 'binary';
  184. };
  185. FileModel.prototype.setUrl = function(url) {
  186. this.addUrl(url);
  187. this.set({
  188. url: url
  189. });
  190. return this;
  191. };
  192. FileModel.prototype.addUrl = function(url) {
  193. var existingUrl, found, newUrl, urls, _i, _j, _len, _len1;
  194. if (url instanceof Array) {
  195. for (_i = 0, _len = url.length; _i < _len; _i++) {
  196. newUrl = url[_i];
  197. this.addUrl(newUrl);
  198. }
  199. } else if (url) {
  200. found = false;
  201. urls = this.get('urls');
  202. for (_j = 0, _len1 = urls.length; _j < _len1; _j++) {
  203. existingUrl = urls[_j];
  204. if (existingUrl === url) {
  205. found = true;
  206. break;
  207. }
  208. }
  209. if (!found) {
  210. urls.push(url);
  211. }
  212. this.trigger('change:urls', this, urls, {});
  213. this.trigger('change', this, {});
  214. }
  215. return this;
  216. };
  217. FileModel.prototype.removeUrl = function(userUrl) {
  218. var index, url, urls, _i, _len;
  219. urls = this.get('urls');
  220. for (index = _i = 0, _len = urls.length; _i < _len; index = ++_i) {
  221. url = urls[index];
  222. if (url === userUrl) {
  223. urls.splice(index, 1);
  224. break;
  225. }
  226. }
  227. return this;
  228. };
  229. FileModel.prototype.getPath = function(relativePath, parentPath) {
  230. var path, relativeDirPath;
  231. if (/^\./.test(relativePath)) {
  232. relativeDirPath = this.get('relativeDirPath');
  233. path = pathUtil.join(relativeDirPath, relativePath);
  234. } else {
  235. if (parentPath) {
  236. path = pathUtil.join(parentPath, relativePath);
  237. } else {
  238. path = relativePath;
  239. }
  240. }
  241. return path;
  242. };
  243. FileModel.prototype.initialize = function(attrs, opts) {
  244. var buffer, data, defaults, detectEncoding, meta, outDirPath, stat;
  245. if (opts == null) {
  246. opts = {};
  247. }
  248. outDirPath = opts.outDirPath, detectEncoding = opts.detectEncoding, stat = opts.stat, data = opts.data, buffer = opts.buffer, meta = opts.meta;
  249. if (detectEncoding != null) {
  250. this.detectEncoding = detectEncoding;
  251. }
  252. if (outDirPath) {
  253. this.outDirPath = outDirPath;
  254. }
  255. defaults = {
  256. extensions: [],
  257. urls: [],
  258. id: this.cid
  259. };
  260. if (stat) {
  261. this.setStat(stat);
  262. } else {
  263. defaults.ctime = new Date();
  264. defaults.mtime = new Date();
  265. }
  266. this.set(defaults);
  267. if (attrs.data != null) {
  268. data = attrs.data;
  269. delete attrs.data;
  270. delete this.attributes.data;
  271. }
  272. if (data != null) {
  273. this.setData(data);
  274. }
  275. if (attrs.buffer != null) {
  276. buffer = attrs.buffer;
  277. delete attrs.buffer;
  278. delete this.attributes.buffer;
  279. }
  280. if (buffer != null) {
  281. this.setBuffer(buffer);
  282. }
  283. if (attrs.meta != null) {
  284. this.setMeta(attrs.meta);
  285. delete attrs.meta;
  286. }
  287. if (meta) {
  288. this.setMeta(meta);
  289. }
  290. return FileModel.__super__.initialize.apply(this, arguments);
  291. };
  292. FileModel.prototype.load = function(opts, next) {
  293. var buffer, data, exists, file, filePath, fullPath, tasks, _ref2, _ref3,
  294. _this = this;
  295. if (opts == null) {
  296. opts = {};
  297. }
  298. _ref2 = extractOptsAndCallback(opts, next), opts = _ref2[0], next = _ref2[1];
  299. file = this;
  300. exists = (_ref3 = opts.exists) != null ? _ref3 : false;
  301. fullPath = this.get('fullPath');
  302. filePath = fullPath || this.get('relativePath') || this.get('filename');
  303. file.log('debug', "Loading the file: " + filePath);
  304. tasks = new TaskGroup().setConfig({
  305. concurrency: 0
  306. }).once('complete', function(err) {
  307. if (err) {
  308. return next(err);
  309. }
  310. file.log('debug', "Loaded the file: " + filePath);
  311. return file.parse(function(err) {
  312. if (err) {
  313. return next(err);
  314. }
  315. return file.normalize(function(err) {
  316. if (err) {
  317. return next(err);
  318. }
  319. return next(null, file.buffer);
  320. });
  321. });
  322. });
  323. data = file.getData();
  324. if (data != null) {
  325. buffer = new Buffer(data);
  326. file.setBuffer(buffer);
  327. }
  328. if (opts.stat) {
  329. file.setStat(opts.stat);
  330. }
  331. if (opts.buffer) {
  332. file.setBuffer(opts.buffer);
  333. }
  334. tasks.addTask(function(complete) {
  335. if (fullPath && exists && (opts.stat != null) === false) {
  336. return safefs.stat(fullPath, function(err, fileStat) {
  337. if (err) {
  338. return complete(err);
  339. }
  340. file.setStat(fileStat);
  341. return complete();
  342. });
  343. } else {
  344. return complete();
  345. }
  346. });
  347. tasks.addTask(function(complete) {
  348. if (fullPath && exists && (opts.buffer != null) === false) {
  349. return safefs.readFile(fullPath, function(err, buffer) {
  350. if (err) {
  351. return complete(err);
  352. }
  353. file.setBuffer(buffer);
  354. return complete();
  355. });
  356. } else {
  357. return complete();
  358. }
  359. });
  360. if (fullPath) {
  361. safefs.exists(fullPath, function(_exists) {
  362. exists = _exists;
  363. file.set({
  364. exists: exists
  365. });
  366. return tasks.run();
  367. });
  368. } else {
  369. tasks.run();
  370. }
  371. return this;
  372. };
  373. FileModel.prototype.parse = function(opts, next) {
  374. var buffer, changes, content, encoding, err, fullPath, isText, source, _ref2, _ref3, _ref4;
  375. if (opts == null) {
  376. opts = {};
  377. }
  378. _ref2 = extractOptsAndCallback(opts, next), opts = _ref2[0], next = _ref2[1];
  379. buffer = this.getBuffer();
  380. fullPath = this.get('fullPath');
  381. encoding = this.get('encoding') || null;
  382. changes = {};
  383. if ((encoding != null) === false || opts.reencode === true) {
  384. isText = balUtil.isTextSync(fullPath, buffer);
  385. if (isText === true) {
  386. if (this.detectEncoding) {
  387. if (jschardet == null) {
  388. jschardet = require('jschardet');
  389. }
  390. try {
  391. if (Iconv == null) {
  392. Iconv = require('iconv').Iconv;
  393. }
  394. } catch (_error) {
  395. err = _error;
  396. Iconv = null;
  397. }
  398. if (encoding == null) {
  399. encoding = ((_ref3 = jschardet.detect(buffer)) != null ? _ref3.encoding : void 0) || 'utf8';
  400. }
  401. } else {
  402. if (encoding == null) {
  403. encoding = 'utf8';
  404. }
  405. }
  406. if ((_ref4 = encoding.toLowerCase()) !== 'ascii' && _ref4 !== 'utf8' && _ref4 !== 'utf-8') {
  407. if (Iconv != null) {
  408. this.log('info', "Converting encoding " + encoding + " to UTF-8 on " + fullPath);
  409. try {
  410. buffer = new Iconv(encoding, 'utf8').convert(buffer);
  411. } catch (_error) {
  412. err = _error;
  413. this.log('warn', "Encoding conversion failed, therefore we cannot convert the encoding " + encoding + " to UTF-8 on " + fullPath);
  414. }
  415. } else {
  416. this.log('warn', "Iconv did not load, therefore we cannot convert the encoding " + encoding + " to UTF-8 on " + fullPath);
  417. }
  418. }
  419. changes.encoding = encoding;
  420. } else {
  421. encoding = 'binary';
  422. changes.encoding = encoding;
  423. }
  424. }
  425. if (encoding === 'binary') {
  426. content = source = '';
  427. changes.content = content;
  428. changes.source = source;
  429. } else {
  430. source = buffer.toString('utf8');
  431. content = source;
  432. changes.content = content;
  433. changes.source = source;
  434. }
  435. this.set(changes);
  436. next();
  437. return this;
  438. };
  439. FileModel.prototype.normalize = function(opts, next) {
  440. var basename, changes, contentType, date, err, extension, extensions, filename, fullDirPath, fullPath, meta, mtime, name, outBasename, outContentType, outDirPath, outExtension, outFilename, outPath, relativeBase, relativeDirPath, relativeOutBase, relativeOutDirPath, relativeOutPath, relativePath, slug, url, _defaultUrl, _ref2;
  441. if (opts == null) {
  442. opts = {};
  443. }
  444. _ref2 = extractOptsAndCallback(opts, next), opts = _ref2[0], next = _ref2[1];
  445. changes = {};
  446. meta = this.getMeta();
  447. filename = opts.filename || this.get('filename') || null;
  448. relativePath = opts.relativePath || this.get('relativePath') || null;
  449. fullPath = opts.fullPath || this.get('fullPath') || null;
  450. mtime = opts.mtime || this.get('mtime') || null;
  451. date = opts.date || meta.get('date') || null;
  452. name = opts.name || meta.get('name') || null;
  453. slug = opts.slug || meta.get('slug') || null;
  454. url = opts.url || meta.get('url') || null;
  455. contentType = opts.contentType || meta.get('contentType') || null;
  456. outContentType = opts.outContentType || meta.get('outContentType') || null;
  457. outFilename = opts.outFilename || meta.get('outFilename') || null;
  458. outExtension = opts.outExtension || meta.get('outExtension') || null;
  459. outPath = opts.outPath || meta.get('outPath') || null;
  460. extensions = null;
  461. extension = null;
  462. basename = null;
  463. outBasename = null;
  464. relativeOutPath = null;
  465. relativeDirPath = null;
  466. relativeOutDirPath = null;
  467. relativeBase = null;
  468. relativeOutBase = null;
  469. outDirPath = null;
  470. fullDirPath = null;
  471. changes.filename = filename = this.getFilename({
  472. filename: filename,
  473. relativePath: relativePath,
  474. fullPath: fullPath
  475. });
  476. if (!filename) {
  477. err = new Error('filename is required, it can be specified via filename, fullPath, or relativePath');
  478. return next(err);
  479. }
  480. if (!relativePath && filename) {
  481. changes.relativePath = relativePath = filename;
  482. }
  483. changes.basename = basename = docpadUtil.getBasename(filename);
  484. changes.extensions = extensions = this.getExtensions({
  485. filename: filename
  486. });
  487. changes.extension = extension = docpadUtil.getExtension(extensions);
  488. if (fullPath) {
  489. changes.fullDirPath = fullDirPath = docpadUtil.getDirPath(fullPath);
  490. }
  491. changes.relativeDirPath = relativeDirPath = docpadUtil.getDirPath(relativePath);
  492. changes.relativeBase = relativeBase = relativeDirPath ? pathUtil.join(relativeDirPath, basename) : basename;
  493. if (!contentType) {
  494. changes.contentType = contentType = mime.lookup(fullPath || relativePath);
  495. }
  496. if (!date) {
  497. changes.date = date = mtime || this.get('date') || new Date();
  498. }
  499. if (!outFilename && !outPath) {
  500. changes.outFilename = outFilename = docpadUtil.getOutFilename(basename, outExtension || extensions.join('.'));
  501. }
  502. if (!outPath) {
  503. changes.outPath = outPath = pathUtil.resolve(this.outDirPath, relativeDirPath, outFilename);
  504. }
  505. changes.outDirPath = outDirPath = docpadUtil.getDirPath(outPath);
  506. changes.outFilename = outFilename = docpadUtil.getFilename(outPath);
  507. changes.outBasename = outBasename = docpadUtil.getBasename(outFilename);
  508. changes.outExtension = outExtension = docpadUtil.getExtension(outFilename);
  509. changes.relativeOutPath = relativeOutPath = outPath.replace(this.outDirPath, '').replace(/^[\/\\]/, '');
  510. changes.relativeOutDirPath = relativeOutDirPath = docpadUtil.getDirPath(relativeOutPath);
  511. changes.relativeOutBase = relativeOutBase = pathUtil.join(relativeOutDirPath, outBasename);
  512. if (!name) {
  513. changes.name = name = outFilename;
  514. }
  515. _defaultUrl = docpadUtil.getUrl(relativeOutPath);
  516. if (url) {
  517. this.setUrl(url);
  518. this.addUrl(_defaultUrl);
  519. } else {
  520. this.setUrl(_defaultUrl);
  521. }
  522. if (!outContentType && contentType) {
  523. changes.outContentType = outContentType = mime.lookup(outPath || relativeOutPath) || contentType;
  524. }
  525. if (!slug) {
  526. changes.slug = slug = docpadUtil.getSlug(relativeOutBase);
  527. }
  528. this.set(changes);
  529. next();
  530. return this;
  531. };
  532. FileModel.prototype.contextualize = function(opts, next) {
  533. var _ref2;
  534. if (opts == null) {
  535. opts = {};
  536. }
  537. _ref2 = extractOptsAndCallback(opts, next), opts = _ref2[0], next = _ref2[1];
  538. next();
  539. return this;
  540. };
  541. FileModel.prototype.write = function(opts, next) {
  542. var err, file, _ref2, _ref3;
  543. _ref2 = extractOptsAndCallback(opts, next), opts = _ref2[0], next = _ref2[1];
  544. file = this;
  545. opts.path || (opts.path = this.get('outPath'));
  546. opts.encoding || (opts.encoding = this.get('encoding'));
  547. opts.content || (opts.content = this.getContent());
  548. opts.type || (opts.type = 'file');
  549. if (!opts.path) {
  550. next();
  551. return this;
  552. }
  553. if ((_ref3 = opts.encoding.toLowerCase()) !== 'ascii' && _ref3 !== 'utf8' && _ref3 !== 'utf-8' && _ref3 !== 'binary') {
  554. if (Iconv != null) {
  555. this.log('info', "Converting encoding UTF-8 to " + opts.encoding + " on " + opts.path);
  556. try {
  557. opts.content = new Iconv('utf8', opts.encoding).convert(opts.content);
  558. } catch (_error) {
  559. err = _error;
  560. this.log('warn', "Encoding conversion failed, therefore we cannot convert the encoding UTF-8 to " + opts.encoding + " on " + opts.path);
  561. }
  562. } else {
  563. this.log('warn', "Iconv did not load, therefore we cannot convert the encoding UTF-8 to " + opts.encoding + " on " + opts.path);
  564. }
  565. }
  566. file.log('debug', "Writing the " + opts.type + ": " + opts.path + " " + opts.encoding);
  567. safefs.writeFile(opts.path, opts.content, function(err) {
  568. if (err) {
  569. return next(err);
  570. }
  571. file.log('debug', "Wrote the " + opts.type + ": " + opts.path + " " + opts.encoding);
  572. return next();
  573. });
  574. return this;
  575. };
  576. FileModel.prototype["delete"] = function(next) {
  577. var file, fileOutPath;
  578. file = this;
  579. fileOutPath = this.get('outPath');
  580. if (!fileOutPath) {
  581. next();
  582. return this;
  583. }
  584. file.log('debug', "Delete the file: " + fileOutPath);
  585. safefs.exists(fileOutPath, function(exists) {
  586. if (!exists) {
  587. return next();
  588. }
  589. return safefs.unlink(fileOutPath, function(err) {
  590. if (err) {
  591. return next(err);
  592. }
  593. file.log('debug', "Deleted the file: " + fileOutPath);
  594. return next();
  595. });
  596. });
  597. return this;
  598. };
  599. return FileModel;
  600. })(Model);
  601. module.exports = FileModel;