1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { existsSync, readFileSync, writeFileSync } from 'fs';
17import { readJsonSync } from 'fs-extra';
18import type { IOptions } from '../configs/IOptions';
19import { fileExtensions } from '../common/type';
20import type { PathAndExtension } from '../common/type';
21import fs from 'fs';
22import path from 'path';
23
24export const BUNDLE = '@bundle:';
25export const NORMALIZE = '@normalized:';
26
27export class FileUtils {
28  /**
29   * Read file and return content
30   *
31   * @param filePath file path
32   */
33  public static readFile(filePath: string): string | undefined {
34    if (!existsSync(filePath)) {
35      console.error(`File <${this.getFileName(filePath)}> is not found.`);
36      return undefined;
37    }
38    return readFileSync(filePath, 'utf-8');
39  }
40
41  /**
42   * Read file and convert to json object.
43   *
44   * @param filePath file path
45   */
46  public static readFileAsJson(filePath: string): IOptions | undefined {
47    if (!existsSync(filePath)) {
48      console.error(`File <${this.getFileName(filePath)}> is not found.`);
49      return undefined;
50    }
51
52    try {
53      return readJsonSync(filePath);
54    } catch (e) {
55      console.error('json file read error: ' + filePath);
56      return undefined;
57    }
58  }
59
60  /**
61   * Get File Name
62   *
63   * @param filePath file path
64   */
65  public static getFileName(filePath: string): string | undefined {
66    if (!filePath) {
67      return undefined;
68    }
69
70    const lastSepIndex: number = filePath.lastIndexOf('/');
71    if (lastSepIndex >= 0) {
72      return filePath.slice(lastSepIndex + 1);
73    }
74
75    return filePath.slice(filePath.lastIndexOf('\\') + 1);
76  }
77
78  /**
79   * Get suffix of a file.
80   *
81   * @param filePath file path
82   */
83  public static getFileExtension(filePath: string): string | undefined {
84    if (!filePath || !filePath.includes('.')) {
85      return undefined;
86    }
87
88    // get file name
89    let fileName: string = this.getFileName(filePath);
90    if (!fileName.includes('.')) {
91      return undefined;
92    }
93
94    return fileName.slice(fileName.lastIndexOf('.') + 1);
95  }
96
97  public static writeFile(filePath: string, content: string): void {
98    writeFileSync(filePath, content);
99  }
100
101  /**
102   * get prefix of directory
103   * @param dirPath
104   */
105  public static getPrefix(dirPath: string): string | undefined {
106    if (!dirPath || (!dirPath.includes('/') && !dirPath.includes('\\'))) {
107      return undefined;
108    }
109
110    const sepIndex: number = dirPath.lastIndexOf('/');
111    if (sepIndex >= 0) {
112      return dirPath.slice(0, sepIndex + 1);
113    }
114
115    return dirPath.slice(0, dirPath.lastIndexOf('\\') + 1);
116  }
117
118  public static getPathWithoutPrefix(filePath: string, prefix: string): string | undefined {
119    if (!filePath.startsWith(prefix)) {
120      return filePath;
121    }
122
123    return filePath.slice(prefix.length);
124  }
125
126  public static splitFilePath(filePath: string): string[] {
127    if (!filePath.includes('\\') && !filePath.includes('\/')) {
128      return [filePath];
129    }
130    const directories = filePath.split(/[\/\\]/);
131    return directories;
132  }
133
134  /**
135   * split the file path and collect the results into the reserved array
136   */
137  public static collectPathReservedString(filePath: string, reservedArray: string[]): void {
138    const directories = this.splitFilePath(filePath);
139    directories.forEach(reservedStr => {
140      reservedArray.push(reservedStr);
141    });
142  }
143
144  static relativePathBegins: string[] = ['./', '../', '.\\', '..\\'];
145  public static isRelativePath(filePath: string): boolean {
146    for (const bebin of this.relativePathBegins) {
147      if (filePath.startsWith(bebin)) {
148        return true;
149      }
150    }
151    return false;
152  }
153
154  public static getFileSuffix(filePath: string): PathAndExtension {
155    for (let ext of fileExtensions) {
156      if (filePath.endsWith(ext)) {
157        const filePathWithoutSuffix: string = filePath.replace(new RegExp(`${ext}$`), '');
158        return { path: filePathWithoutSuffix, ext: ext };
159      }
160    }
161    return { path: filePath, ext: '' };
162  }
163
164  public static isReadableFile(filePath: string): boolean {
165    try {
166      fs.accessSync(filePath, fs.constants.R_OK);
167    } catch (err) {
168      return false;
169    }
170    return true;
171  }
172
173  public static toUnixPath(data: string): string {
174    if (/^win/.test(require('os').platform())) {
175      const fileTmps: string[] = data.split(path.sep);
176      const newData: string = path.posix.join(...fileTmps);
177      return newData;
178    }
179    return data;
180  }
181
182  public static getAbsPathBaseConfigPath(configPath: string, relativePath: string): string {
183    const absPath: string = path.join(path.dirname(configPath), relativePath);
184    return this.toUnixPath(absPath);
185  }
186
187  public static deleteFile(filePath: string): void {
188    if (fs.existsSync(filePath)) {
189        fs.unlinkSync(filePath);
190    }
191  }
192}
193