UNPKG

4.04 kBJavaScriptView Raw
1const R = require('ramda');
2const fs = require('fs-extra');
3const path = require('path');
4
5const CliFileNotFoundError = require('@src/exceptions/cli-file-not-found-error');
6const jsonView = require('@src/view/json-view');
7
8const yaml = require('./yaml-parser');
9
10const FILE_EXTNAME_JSON = '.json';
11const FILE_EXTNAME_YAML = '.yaml';
12const FILE_EXTNAME_YML = '.yml';
13const FILE_TYPE_JSON = 'JSON';
14const FILE_TYPE_YAML = 'YAML';
15
16const READ_METHOD_BY_FILE_TYPE = {
17 JSON: (filePath) => {
18 try {
19 return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
20 } catch (error) {
21 throw new Error(`Failed to parse JSON file ${filePath}.\n${error}`);
22 }
23 },
24 YAML: (filePath) => {
25 try {
26 return yaml.load(filePath);
27 } catch (error) {
28 throw new Error(`Failed to parse YAML file ${filePath}.\n${error}`);
29 }
30 }
31};
32
33const WRITE_METHOD_BY_FILE_TYPE = {
34 JSON: (filePath, content) => {
35 fs.writeFileSync(filePath, jsonView.toString(content), 'utf-8');
36 },
37 YAML: (filePath, content) => {
38 yaml.dump(filePath, content);
39 }
40};
41
42module.exports = class ConfigFile {
43 /**
44 * Constructor function for ConfigFile. Current support JSON and YAML.
45 * @param {string} filePath
46 * @throws {Error}
47 */
48 constructor(filePath) {
49 this.path = filePath;
50 this.content = null;
51 this.fileType = null;
52
53 this._validateFilePath();
54 this.read();
55 }
56
57 /**
58 * Write content to file path using ConfigFile's WRITE_METHODs
59 * @param {String} filePath
60 * @param {Object} content
61 */
62 static withContent(filePath, content) {
63 // ensure file exits with content
64 if (fs.existsSync(filePath)) {
65 throw new Error(`Failed to create file ${filePath} as it already exists.`);
66 }
67 fs.ensureDirSync(path.dirname(filePath));
68 const fileType = _resolveFileType(filePath);
69 WRITE_METHOD_BY_FILE_TYPE[fileType](filePath, content);
70 }
71
72 /**
73 * Get property based on the property array.
74 * Return undefined if not found.
75 * @param {string} pathArray e.g. ['path', 'to', 'the', '3rd', 'object', 2, 'done']
76 */
77 getProperty(pathArray) {
78 return R.view(R.lensPath(pathArray), this.content);
79 }
80
81 /**
82 * Set property to the runtime object based on the property array.
83 * Create field if path does not exist.
84 * @param {string} pathArray
85 * @param {string} newValue
86 */
87 setProperty(pathArray, newValue) {
88 this.content = R.set(R.lensPath(pathArray), newValue, this.content);
89 }
90
91 /**
92 * Write file according to the file path and serialize it based on file extname
93 */
94 write() {
95 WRITE_METHOD_BY_FILE_TYPE[this.fileType](this.path, this.content);
96 }
97
98 /**
99 * Read from file path and deserialize it based on file extname
100 */
101 read() {
102 this.content = READ_METHOD_BY_FILE_TYPE[this.fileType](this.path);
103 }
104
105 _validateFilePath() {
106 // check existence
107 if (!fs.existsSync(this.path)) {
108 throw new CliFileNotFoundError(`File ${this.path} not exists.`);
109 }
110 // check access permission
111 try {
112 fs.accessSync(this.path, fs.constants.R_OK | fs.constants.W_OK);
113 } catch (error) {
114 throw new Error(`No access to read/write file ${this.path}.`);
115 }
116 this.fileType = _resolveFileType(this.path);
117 }
118};
119
120function _resolveFileType(filePath) {
121 if (path.basename(filePath) === 'cli_config') {
122 return FILE_TYPE_JSON;
123 }
124 // check file extension
125 const fileExtension = path.extname(filePath).toLowerCase();
126 if (fileExtension === FILE_EXTNAME_JSON) {
127 return FILE_TYPE_JSON;
128 }
129 if (fileExtension === FILE_EXTNAME_YAML || fileExtension === FILE_EXTNAME_YML) {
130 return FILE_TYPE_YAML;
131 }
132
133 throw new Error(`File type for ${filePath} is not supported. Only JSON and YAML files are supported.`);
134}