UNPKG

4.03 kBJavaScriptView Raw
1"use strict";
2
3const pathUtil = require("path");
4const fs = require("./utils/fs");
5const validate = require("./utils/validate");
6const dir = require("./dir");
7
8const validateInput = (methodName, path, data, options) => {
9 const methodSignature = `${methodName}(path, data, [options])`;
10 validate.argument(methodSignature, "path", path, ["string"]);
11 validate.argument(methodSignature, "data", data, [
12 "string",
13 "buffer",
14 "object",
15 "array"
16 ]);
17 validate.options(methodSignature, "options", options, {
18 atomic: ["boolean"],
19 jsonIndent: ["number"]
20 });
21};
22
23// Temporary file extensions used for atomic file overwriting.
24const newExt = ".__new__";
25
26const serializeToJsonMaybe = (data, jsonIndent) => {
27 let indent = jsonIndent;
28 if (typeof indent !== "number") {
29 indent = 2;
30 }
31
32 if (typeof data === "object" && !Buffer.isBuffer(data) && data !== null) {
33 return JSON.stringify(data, null, indent);
34 }
35
36 return data;
37};
38
39// ---------------------------------------------------------
40// SYNC
41// ---------------------------------------------------------
42
43const writeFileSync = (path, data, options) => {
44 try {
45 fs.writeFileSync(path, data, options);
46 } catch (err) {
47 if (err.code === "ENOENT") {
48 // Means parent directory doesn't exist, so create it and try again.
49 dir.createSync(pathUtil.dirname(path));
50 fs.writeFileSync(path, data, options);
51 } else {
52 throw err;
53 }
54 }
55};
56
57const writeAtomicSync = (path, data, options) => {
58 // we are assuming there is file on given path, and we don't want
59 // to touch it until we are sure our data has been saved correctly,
60 // so write the data into temporary file...
61 writeFileSync(path + newExt, data, options);
62 // ...next rename temp file to replace real path.
63 fs.renameSync(path + newExt, path);
64};
65
66const writeSync = (path, data, options) => {
67 const opts = options || {};
68 const processedData = serializeToJsonMaybe(data, opts.jsonIndent);
69
70 let writeStrategy = writeFileSync;
71 if (opts.atomic) {
72 writeStrategy = writeAtomicSync;
73 }
74 writeStrategy(path, processedData, { mode: opts.mode });
75};
76
77// ---------------------------------------------------------
78// ASYNC
79// ---------------------------------------------------------
80
81const writeFileAsync = (path, data, options) => {
82 return new Promise((resolve, reject) => {
83 fs.writeFile(path, data, options)
84 .then(resolve)
85 .catch(err => {
86 // First attempt to write a file ended with error.
87 // Check if this is not due to nonexistent parent directory.
88 if (err.code === "ENOENT") {
89 // Parent directory doesn't exist, so create it and try again.
90 dir
91 .createAsync(pathUtil.dirname(path))
92 .then(() => {
93 return fs.writeFile(path, data, options);
94 })
95 .then(resolve, reject);
96 } else {
97 // Nope, some other error, throw it.
98 reject(err);
99 }
100 });
101 });
102};
103
104const writeAtomicAsync = (path, data, options) => {
105 return new Promise((resolve, reject) => {
106 // We are assuming there is file on given path, and we don't want
107 // to touch it until we are sure our data has been saved correctly,
108 // so write the data into temporary file...
109 writeFileAsync(path + newExt, data, options)
110 .then(() => {
111 // ...next rename temp file to real path.
112 return fs.rename(path + newExt, path);
113 })
114 .then(resolve, reject);
115 });
116};
117
118const writeAsync = (path, data, options) => {
119 const opts = options || {};
120 const processedData = serializeToJsonMaybe(data, opts.jsonIndent);
121
122 let writeStrategy = writeFileAsync;
123 if (opts.atomic) {
124 writeStrategy = writeAtomicAsync;
125 }
126 return writeStrategy(path, processedData, { mode: opts.mode });
127};
128
129// ---------------------------------------------------------
130// API
131// ---------------------------------------------------------
132
133exports.validateInput = validateInput;
134exports.sync = writeSync;
135exports.async = writeAsync;