UNPKG

6.02 kBPlain TextView Raw
1import {getPathNodes, normalizePathNodes} from "./utils";
2
3export const pathSeparator = '/';
4
5
6export type DirectoryContent = { [key: string]: string | DirectoryContent }
7
8
9export interface FileSystemNode {
10 type: 'dir' | 'file';
11 name: string,
12 fullPath: string,
13}
14
15export class ShallowDirectory implements FileSystemNode {
16 public type: 'dir' = 'dir';
17
18 constructor(public name: string,
19 public fullPath: string,) {
20 }
21}
22
23export class Directory implements FileSystemNode {
24 public type: 'dir' = 'dir';
25
26 constructor(public name: string,
27 public fullPath: string,
28 public children: Array<File | Directory> = []) {
29 }
30
31 static getSubDir(directory: Directory, path: string | string[]): Directory | null {
32 const pathArr = typeof path === 'string' ? getPathNodes(path) : path;
33 while (pathArr.length) {
34 const targetName = pathArr.shift();
35 if (targetName && directory.children) {
36 const node = directory.children.find(({name}) => name === targetName);
37 if (isDir(node)) {
38 directory = node;
39 } else {
40 return null;
41 }
42 } else {
43 return null;
44 }
45 }
46 return directory;
47 }
48
49 static clone(node: Directory, path: string | string[] = []): Directory {
50 const pathArr = typeof path === 'string' ? getPathNodes(path) : path;
51 if (!pathArr.length && node.name) {
52 pathArr.push(node.name);
53 }
54 return new Directory(
55 pathArr.length ? pathArr[pathArr.length - 1] : node.name,
56 normalizePathNodes(pathArr),
57 node.children.map(child => {
58 let childPath = pathArr.concat([child.name]);
59 if (isDir(child)) {
60 return this.clone(child, childPath);
61 } else {
62 return new File(child.name, normalizePathNodes(childPath), child.content);
63 }
64 })
65 )
66 }
67
68 static cloneStructure(node: Directory): Directory {
69 return new Directory(
70 node.name,
71 node.fullPath,
72 node.children.map(child => isDir(child) ? this.cloneStructure(child) : new File(child.name, child.fullPath))
73 )
74 }
75
76 static fromContent(content: DirectoryContent, name: string = '', location: string = ''): Directory {
77 if (location === pathSeparator) {
78 location = '';
79 } else if (location.length && !location.endsWith(pathSeparator)) {
80 location = location + pathSeparator;
81 }
82 let path = location.length ? location + name : name;
83 let childLocation = path.length ? path + pathSeparator : name;
84 return new Directory(name, path,
85 Object.keys(content).map(contentPartPath => {
86 const fileContent = content[contentPartPath];
87 const fullPath = childLocation + contentPartPath;
88 if (typeof fileContent === 'string') {
89 return new File(contentPartPath, fullPath, fileContent);
90 } else {
91 return this.fromContent(fileContent, contentPartPath, childLocation);
92 }
93 }));
94 }
95
96 static toContent(directory: Directory): DirectoryContent {
97 const result: DirectoryContent = {};
98 directory.children.forEach(child => {
99 if (child.type === 'file') {
100 if (child.content === undefined) {
101 throw new Error('file not loaded : ' + child.fullPath);
102 }
103 result[child.name] = child.content;
104 } else if (child.type === 'dir') {
105 result[child.name] = this.toContent(child);
106 }
107 });
108 return result;
109 }
110
111 /**
112 * add one directory to another, in-place
113 * @returns {Directory} subject argument
114 */
115 static mix(subject: Directory, mixin: Directory): Directory {
116 mixin.children.forEach(mixChild => {
117 const subjChild = subject.children.find(({name}) => name === mixChild.name);
118 if (isFile(mixChild)) {
119 if (mixChild.content === undefined) {
120 throw new Error('file not loaded : ' + mixChild.fullPath);
121 }
122 if (subjChild) {
123 if (isFile(subjChild)) {
124 subjChild.content = mixChild.content;
125 } else if (isDir(subjChild)) {
126 throw new Error(`can't override directory with file : ${mixChild.fullPath} , ${subjChild.fullPath}`);
127 }
128 } else {
129 subject.children.push(new File(mixChild.name, normalizePathNodes([subject.fullPath, mixChild.name]), mixChild.content));
130 }
131 } else if (isDir(mixChild)) {
132 if (subjChild) {
133 if (isFile(subjChild)) {
134 throw new Error(`can't file directory with directory : ${mixChild.fullPath} , ${subjChild.fullPath}`);
135 } else if (isDir(subjChild)) {
136 this.mix(subjChild, mixChild);
137 }
138 } else {
139 subject.children.push(Directory.clone(mixChild, [subject.fullPath, mixChild.name]));
140 }
141 }
142 });
143
144 return subject;
145 }
146}
147
148export class File implements FileSystemNode {
149 public type: 'file' = 'file';
150 public content?: string;
151
152 constructor(public name: string,
153 public fullPath: string,
154 content?: string) {
155 if (content) this.content = content;
156 }
157}
158
159export function isFile(node?: FileSystemNode | null): node is File {
160 if (!node) return false;
161 return node.type === 'file';
162}
163
164export function isDir(node?: FileSystemNode | null): node is Directory {
165 if (!node) return false;
166 return node.type === 'dir';
167}