@himamshus06/git-auto 1.3.1 → 1.3.3
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 +140 -30
package/package.json
CHANGED
package/src/tree.js
CHANGED
|
@@ -2,24 +2,56 @@ const fs = require('node:fs');
|
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Custom error for tree parsing failures
|
|
6
|
+
*/
|
|
7
|
+
class TreeParseError extends Error {
|
|
8
|
+
constructor(message, line) {
|
|
9
|
+
super(line ? `Line ${line}: ${message}` : message);
|
|
10
|
+
this.name = 'TreeParseError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Symbols used to define tree structures
|
|
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
|
+
*
|
|
6
35
|
* @param {string} text - The visual directory tree string.
|
|
7
|
-
* @returns {
|
|
36
|
+
* @returns {Array<{name: string, depth: number, isDirectory: boolean, fullPath: string}>}
|
|
37
|
+
* @throws {TreeParseError}
|
|
8
38
|
*/
|
|
9
|
-
|
|
39
|
+
function parseTree(text) {
|
|
10
40
|
if (!text || text.trim() === '') {
|
|
11
|
-
throw new
|
|
41
|
+
throw new TreeParseError('No tree provided');
|
|
12
42
|
}
|
|
13
43
|
|
|
14
44
|
const lines = text.split('\n').filter(line => line.trim() !== '');
|
|
15
45
|
const stack = [];
|
|
16
|
-
const
|
|
17
|
-
|
|
46
|
+
const result = [];
|
|
47
|
+
let currentDepth = -1;
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < lines.length; i++) {
|
|
50
|
+
const line = lines[i];
|
|
51
|
+
const lineNum = i + 1;
|
|
18
52
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// We find the first character that isn't a tree symbol or whitespace
|
|
22
|
-
const match = line.match(/^([│\s├└]*)(.*)$/);
|
|
53
|
+
// Regex to split prefix symbols/spaces from the actual name
|
|
54
|
+
const match = line.match(/^([│\s├└| \+`-]*) (.*)$/) || line.match(/^([│\s├└| \+`-]*)(.*)$/);
|
|
23
55
|
if (!match) continue;
|
|
24
56
|
|
|
25
57
|
const prefix = match[1];
|
|
@@ -27,41 +59,119 @@ async function parseAndCreateTree(text) {
|
|
|
27
59
|
|
|
28
60
|
if (!name) continue;
|
|
29
61
|
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
62
|
+
// Calculate depth based on "indentation units"
|
|
63
|
+
// A unit is typically 4 characters (symbol + 3 spaces) or similar.
|
|
64
|
+
// We count how many vertical bars or space blocks exist before the branch symbol.
|
|
65
|
+
const depth = calculateDepth(prefix);
|
|
33
66
|
|
|
34
|
-
//
|
|
67
|
+
// Logical Validation: Continuity Check
|
|
68
|
+
if (depth > currentDepth + 1) {
|
|
69
|
+
throw new TreeParseError(`Nesting depth jump detected (from ${currentDepth + 1} to ${depth})`, lineNum);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Adjust stack to current depth
|
|
35
73
|
while (stack.length > depth) {
|
|
36
74
|
stack.pop();
|
|
37
75
|
}
|
|
38
76
|
|
|
39
|
-
//
|
|
40
|
-
const isDirectory = name.endsWith('/') || !name.includes('.');
|
|
77
|
+
// Reliable Type Detection
|
|
78
|
+
const isDirectory = name.endsWith('/') || (!name.includes('.') && !name.startsWith('.'));
|
|
41
79
|
const cleanName = isDirectory ? name.replace(/\/$/, '') : name;
|
|
42
80
|
|
|
43
81
|
stack.push(cleanName);
|
|
44
|
-
|
|
45
|
-
// 4. Form full path
|
|
46
82
|
const fullPath = path.join(...stack);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
83
|
+
|
|
84
|
+
result.push({
|
|
85
|
+
name: cleanName,
|
|
86
|
+
depth,
|
|
87
|
+
isDirectory,
|
|
88
|
+
fullPath
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
currentDepth = depth;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Determines the depth of a line based on its prefix.
|
|
99
|
+
* Supports both Unicode and ASCII styles.
|
|
100
|
+
*
|
|
101
|
+
* @param {string} prefix
|
|
102
|
+
* @returns {number}
|
|
103
|
+
*/
|
|
104
|
+
function calculateDepth(prefix) {
|
|
105
|
+
if (!prefix) return 0;
|
|
106
|
+
|
|
107
|
+
// Standard visual trees use blocks of 4 characters per level (e.g., "│ " or " ")
|
|
108
|
+
// We count these blocks.
|
|
109
|
+
let depth = 0;
|
|
110
|
+
let i = 0;
|
|
111
|
+
while (i < prefix.length) {
|
|
112
|
+
// Check if we've reached the terminal branch symbol (├ or └ or + or `)
|
|
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
|
+
}
|
|
117
|
+
|
|
118
|
+
// Increment depth for every "unit" of indentation
|
|
119
|
+
// We assume a unit is 4 characters wide in most common formats
|
|
120
|
+
depth++;
|
|
121
|
+
i += 4;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Because we increment depth for every 4-char block,
|
|
125
|
+
// we need to handle cases where the prefix is shorter than a full block.
|
|
126
|
+
// A more robust way is to count actual vertical markers.
|
|
127
|
+
const markers = (prefix.match(/[│|]/g) || []).length;
|
|
128
|
+
|
|
129
|
+
// Heuristic: If we have markers, that's our depth.
|
|
130
|
+
// Otherwise, we use the space-based block count.
|
|
131
|
+
if (markers > 0) return markers;
|
|
132
|
+
|
|
133
|
+
return Math.floor(prefix.length / 4);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Parses a visual directory tree and creates the corresponding directories/files.
|
|
138
|
+
*
|
|
139
|
+
* @param {string} text - The visual directory tree string.
|
|
140
|
+
* @returns {Promise<{created: string[], errors: string[]}>}
|
|
141
|
+
*/
|
|
142
|
+
async function parseAndCreateTree(text) {
|
|
143
|
+
const created = [];
|
|
144
|
+
const errors = [];
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const treeStructure = parseTree(text);
|
|
148
|
+
|
|
149
|
+
for (const item of treeStructure) {
|
|
150
|
+
try {
|
|
151
|
+
if (item.isDirectory) {
|
|
152
|
+
fs.mkdirSync(item.fullPath, { recursive: true });
|
|
153
|
+
} else {
|
|
154
|
+
const parentDir = path.dirname(item.fullPath);
|
|
155
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
156
|
+
fs.writeFileSync(item.fullPath, '');
|
|
157
|
+
}
|
|
158
|
+
created.push(item.fullPath);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
errors.push(`Failed to create ${item.fullPath}: ${err.message}`);
|
|
55
161
|
}
|
|
56
|
-
created.push(fullPath);
|
|
57
|
-
} catch (err) {
|
|
58
|
-
errors.push(`Failed to create ${fullPath}: ${err.message}`);
|
|
59
162
|
}
|
|
163
|
+
} catch (err) {
|
|
164
|
+
if (err instanceof TreeParseError) {
|
|
165
|
+
throw err; // Rethrow parsing errors for the CLI to handle
|
|
166
|
+
}
|
|
167
|
+
throw new Error(`Unexpected error: ${err.message}`);
|
|
60
168
|
}
|
|
61
169
|
|
|
62
170
|
return { created, errors };
|
|
63
171
|
}
|
|
64
172
|
|
|
65
173
|
module.exports = {
|
|
66
|
-
parseAndCreateTree
|
|
174
|
+
parseAndCreateTree,
|
|
175
|
+
parseTree,
|
|
176
|
+
TreeParseError
|
|
67
177
|
};
|