@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tree.js +140 -30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@himamshus06/git-auto",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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
- * Parses a visual directory tree and creates the corresponding directories.
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 {Promise<{created: string[], errors: string[]}>}
36
+ * @returns {Array<{name: string, depth: number, isDirectory: boolean, fullPath: string}>}
37
+ * @throws {TreeParseError}
8
38
  */
9
- async function parseAndCreateTree(text) {
39
+ function parseTree(text) {
10
40
  if (!text || text.trim() === '') {
11
- throw new Error('No tree provided');
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 created = [];
17
- const errors = [];
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
- for (const line of lines) {
20
- // 1. Determine depth
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
- // Depth is determined by the number of "blocks" of indentation.
31
- // Most trees use 4 chars per level (e.g., "│ " or "├── ").
32
- const depth = Math.floor(prefix.length / 4);
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
- // 2. Adjust stack to current depth
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
- // 3. Clean name (remove trailing slash for directory creation)
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
- try {
48
- if (isDirectory) {
49
- fs.mkdirSync(fullPath, { recursive: true });
50
- } else {
51
- // Ensure parent directory exists
52
- const parentDir = path.dirname(fullPath);
53
- fs.mkdirSync(parentDir, { recursive: true });
54
- fs.writeFileSync(fullPath, ''); // Create empty file
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
  };