@himamshus06/git-auto 1.3.3 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/tree.js +119 -84
package/package.json
CHANGED
package/src/tree.js
CHANGED
|
@@ -12,73 +12,66 @@ class TreeParseError extends Error {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*/
|
|
17
|
-
const SYMBOLS = {
|
|
18
|
-
unicode: {
|
|
19
|
-
vertical: '│',
|
|
20
|
-
branch: '├',
|
|
21
|
-
leaf: '└',
|
|
22
|
-
space: ' '
|
|
23
|
-
},
|
|
24
|
-
ascii: {
|
|
25
|
-
vertical: '|',
|
|
26
|
-
branch: '+',
|
|
27
|
-
leaf: '`',
|
|
28
|
-
space: ' '
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Parses a visual tree string into a structured array of items.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} text - The visual directory tree string.
|
|
36
|
-
* @returns {Array<{name: string, depth: number, isDirectory: boolean, fullPath: string}>}
|
|
37
|
-
* @throws {TreeParseError}
|
|
15
|
+
* Parse a visual directory tree into structured items.
|
|
38
16
|
*/
|
|
39
17
|
function parseTree(text) {
|
|
40
18
|
if (!text || text.trim() === '') {
|
|
41
19
|
throw new TreeParseError('No tree provided');
|
|
42
20
|
}
|
|
43
21
|
|
|
44
|
-
const lines = text
|
|
45
|
-
|
|
22
|
+
const lines = text
|
|
23
|
+
.split(/\r?\n/)
|
|
24
|
+
.filter(line => line.trim() !== '');
|
|
25
|
+
|
|
46
26
|
const result = [];
|
|
47
|
-
|
|
27
|
+
const stack = [];
|
|
48
28
|
|
|
49
29
|
for (let i = 0; i < lines.length; i++) {
|
|
50
30
|
const line = lines[i];
|
|
51
31
|
const lineNum = i + 1;
|
|
52
32
|
|
|
53
|
-
|
|
54
|
-
const match = line.match(/^([│\s├└| \+`-]*) (.*)$/) || line.match(/^([│\s├└| \+`-]*)(.*)$/);
|
|
55
|
-
if (!match) continue;
|
|
33
|
+
const parsed = parseLine(line);
|
|
56
34
|
|
|
57
|
-
|
|
58
|
-
|
|
35
|
+
if (!parsed) {
|
|
36
|
+
throw new TreeParseError(
|
|
37
|
+
`Unable to parse tree line: "${line}"`,
|
|
38
|
+
lineNum
|
|
39
|
+
);
|
|
40
|
+
}
|
|
59
41
|
|
|
60
|
-
|
|
42
|
+
const { name, depth } = parsed;
|
|
61
43
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
44
|
+
// Root must be depth 0
|
|
45
|
+
if (i === 0 && depth !== 0) {
|
|
46
|
+
throw new TreeParseError(
|
|
47
|
+
'Root item must have depth 0',
|
|
48
|
+
lineNum
|
|
49
|
+
);
|
|
50
|
+
}
|
|
66
51
|
|
|
67
|
-
//
|
|
68
|
-
if (depth >
|
|
69
|
-
throw new TreeParseError(
|
|
52
|
+
// Prevent impossible jumps
|
|
53
|
+
if (depth > stack.length) {
|
|
54
|
+
throw new TreeParseError(
|
|
55
|
+
`Nesting depth jump detected`,
|
|
56
|
+
lineNum
|
|
57
|
+
);
|
|
70
58
|
}
|
|
71
59
|
|
|
72
|
-
//
|
|
60
|
+
// Remove anything deeper than the current item
|
|
73
61
|
while (stack.length > depth) {
|
|
74
62
|
stack.pop();
|
|
75
63
|
}
|
|
76
64
|
|
|
77
|
-
//
|
|
78
|
-
const isDirectory =
|
|
79
|
-
const cleanName = isDirectory ? name.replace(/\/$/, '') : name;
|
|
65
|
+
// Detect directory/file
|
|
66
|
+
const isDirectory = detectDirectory(name);
|
|
80
67
|
|
|
68
|
+
const cleanName = isDirectory
|
|
69
|
+
? name.replace(/\/$/, '')
|
|
70
|
+
: name;
|
|
71
|
+
|
|
72
|
+
// Add current item to stack
|
|
81
73
|
stack.push(cleanName);
|
|
74
|
+
|
|
82
75
|
const fullPath = path.join(...stack);
|
|
83
76
|
|
|
84
77
|
result.push({
|
|
@@ -87,57 +80,86 @@ function parseTree(text) {
|
|
|
87
80
|
isDirectory,
|
|
88
81
|
fullPath
|
|
89
82
|
});
|
|
90
|
-
|
|
91
|
-
currentDepth = depth;
|
|
92
83
|
}
|
|
93
84
|
|
|
94
85
|
return result;
|
|
95
86
|
}
|
|
96
87
|
|
|
97
88
|
/**
|
|
98
|
-
*
|
|
99
|
-
* Supports both Unicode and ASCII styles.
|
|
100
|
-
*
|
|
101
|
-
* @param {string} prefix
|
|
102
|
-
* @returns {number}
|
|
89
|
+
* Parse one line of a tree.
|
|
103
90
|
*/
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (prefix[i] === SYMBOLS.unicode.branch || prefix[i] === SYMBOLS.unicode.leaf ||
|
|
114
|
-
prefix[i] === SYMBOLS.ascii.branch || prefix[i] === SYMBOLS.ascii.leaf) {
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
91
|
+
function parseLine(line) {
|
|
92
|
+
// Root:
|
|
93
|
+
// project/
|
|
94
|
+
if (!/^[│|├└+`]/.test(line)) {
|
|
95
|
+
return {
|
|
96
|
+
name: line.trim(),
|
|
97
|
+
depth: 0
|
|
98
|
+
};
|
|
99
|
+
}
|
|
117
100
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
101
|
+
/*
|
|
102
|
+
* Match tree indentation.
|
|
103
|
+
*
|
|
104
|
+
* Examples:
|
|
105
|
+
*
|
|
106
|
+
* ├── src/
|
|
107
|
+
* └── package.json
|
|
108
|
+
*
|
|
109
|
+
* │ ├── components/
|
|
110
|
+
* │ └── App.jsx
|
|
111
|
+
*
|
|
112
|
+
* │ │ └── Button.jsx
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
const match = line.match(/^((?:│ | )*)(?:├── |└── |\+-- |`-- )(.*)$/);
|
|
116
|
+
|
|
117
|
+
if (!match) {
|
|
118
|
+
return null;
|
|
122
119
|
}
|
|
123
120
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// A more robust way is to count actual vertical markers.
|
|
127
|
-
const markers = (prefix.match(/[│|]/g) || []).length;
|
|
121
|
+
const indentation = match[1];
|
|
122
|
+
const name = match[2].trim();
|
|
128
123
|
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
if (markers > 0) return markers;
|
|
124
|
+
// Every 4-character indentation block = one level.
|
|
125
|
+
const depth = indentation.length / 4 + 1;
|
|
132
126
|
|
|
133
|
-
return
|
|
127
|
+
return {
|
|
128
|
+
name,
|
|
129
|
+
depth
|
|
130
|
+
};
|
|
134
131
|
}
|
|
135
132
|
|
|
136
133
|
/**
|
|
137
|
-
*
|
|
134
|
+
* Determine whether an item is a directory.
|
|
138
135
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
136
|
+
* Best case: directories end with /.
|
|
137
|
+
*
|
|
138
|
+
* For trees that don't include /, this falls back to
|
|
139
|
+
* extension-based detection.
|
|
140
|
+
*/
|
|
141
|
+
function detectDirectory(name) {
|
|
142
|
+
// Explicit directory marker
|
|
143
|
+
if (name.endsWith('/')) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Hidden files like .gitignore are files
|
|
148
|
+
if (name.startsWith('.')) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Files with extensions are files
|
|
153
|
+
if (path.extname(name) !== '') {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Otherwise assume directory
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Parse tree and create files/directories.
|
|
141
163
|
*/
|
|
142
164
|
async function parseAndCreateTree(text) {
|
|
143
165
|
const created = [];
|
|
@@ -149,29 +171,42 @@ async function parseAndCreateTree(text) {
|
|
|
149
171
|
for (const item of treeStructure) {
|
|
150
172
|
try {
|
|
151
173
|
if (item.isDirectory) {
|
|
152
|
-
fs.mkdirSync(item.fullPath, {
|
|
174
|
+
fs.mkdirSync(item.fullPath, {
|
|
175
|
+
recursive: true
|
|
176
|
+
});
|
|
153
177
|
} else {
|
|
154
178
|
const parentDir = path.dirname(item.fullPath);
|
|
155
|
-
|
|
179
|
+
|
|
180
|
+
fs.mkdirSync(parentDir, {
|
|
181
|
+
recursive: true
|
|
182
|
+
});
|
|
183
|
+
|
|
156
184
|
fs.writeFileSync(item.fullPath, '');
|
|
157
185
|
}
|
|
186
|
+
|
|
158
187
|
created.push(item.fullPath);
|
|
159
188
|
} catch (err) {
|
|
160
|
-
errors.push(
|
|
189
|
+
errors.push(
|
|
190
|
+
`Failed to create ${item.fullPath}: ${err.message}`
|
|
191
|
+
);
|
|
161
192
|
}
|
|
162
193
|
}
|
|
163
194
|
} catch (err) {
|
|
164
195
|
if (err instanceof TreeParseError) {
|
|
165
|
-
throw err;
|
|
196
|
+
throw err;
|
|
166
197
|
}
|
|
198
|
+
|
|
167
199
|
throw new Error(`Unexpected error: ${err.message}`);
|
|
168
200
|
}
|
|
169
201
|
|
|
170
|
-
return {
|
|
202
|
+
return {
|
|
203
|
+
created,
|
|
204
|
+
errors
|
|
205
|
+
};
|
|
171
206
|
}
|
|
172
207
|
|
|
173
208
|
module.exports = {
|
|
174
209
|
parseAndCreateTree,
|
|
175
210
|
parseTree,
|
|
176
211
|
TreeParseError
|
|
177
|
-
};
|
|
212
|
+
};
|