Skip to content

Commit 91ea9af

Browse files
committed
feat(output): add multiple output formats support 📁
- Add JSON format output for structured data - Add Markdown format output for documentation - Add GenerateOptions interface for format configuration - Add TreeNode interface for structured output - Add Generator class for format handling - Update API to support format parameter - Update documentation with format examples - Update installation command to JSR format
1 parent d5031f5 commit 91ea9af

File tree

5 files changed

+343
-26
lines changed

5 files changed

+343
-26
lines changed

README.md

Lines changed: 129 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ A file tree generator for Deno. Generate beautiful directory trees instantly fro
66

77
- [Installation](#installation)
88
- [Quick Start](#quick-start)
9+
- [Multiple Output Formats](#multiple-output-formats)
910
- [Configuration](#configuration)
1011
- [Tree Options](#tree-options)
1112
- [Performance Settings](#performance-settings)
@@ -18,7 +19,9 @@ A file tree generator for Deno. Generate beautiful directory trees instantly fro
1819
- [API Reference](#api-reference)
1920
- [Main API](#main-api)
2021
- [Configuration Options](#configuration-options)
22+
- [Generate Options](#generate-options)
2123
- [File Metadata](#file-metadata)
24+
- [Tree Node Structure](#tree-node-structure)
2225
- [Troubleshooting](#troubleshooting)
2326
- [Common Issues](#common-issues)
2427
- [Debug Mode](#debug-mode)
@@ -28,7 +31,7 @@ A file tree generator for Deno. Generate beautiful directory trees instantly fro
2831
## Installation
2932

3033
```bash
31-
deno install @neabyte/deno-tree
34+
deno add jsr:@neabyte/deno-tree
3235
```
3336

3437
## Quick Start
@@ -66,6 +69,84 @@ console.log(result)
6669
└── README.md
6770
```
6871

72+
### Multiple Output Formats
73+
74+
```ts
75+
import tree from '@neabyte/deno-tree'
76+
77+
await tree.init('/path/to/directory', {
78+
ignoreDirs: ['node_modules', '.git', 'dist'],
79+
maxFiles: 1000,
80+
showHidden: false,
81+
maxDepth: 3
82+
})
83+
84+
// Tree format (default)
85+
const treeOutput = await tree.generate('/path/to/directory')
86+
console.log(treeOutput)
87+
88+
// JSON format
89+
const jsonOutput = await tree.generate('/path/to/directory', { format: 'json' })
90+
console.log(jsonOutput)
91+
92+
// Markdown format
93+
const markdownOutput = await tree.generate('/path/to/directory', { format: 'markdown' })
94+
console.log(markdownOutput)
95+
```
96+
97+
**Format Examples:**
98+
99+
**Tree Format:**
100+
101+
```
102+
.
103+
├── src/
104+
│ ├── components/
105+
│ │ ├── Button.tsx
106+
│ │ └── Modal.tsx
107+
│ └── index.ts
108+
├── deno.json
109+
└── README.md
110+
```
111+
112+
**JSON Format:**
113+
114+
```json
115+
{
116+
"name": "project",
117+
"type": "directory",
118+
"path": "/path/to/project",
119+
"children": [
120+
{
121+
"name": "src",
122+
"type": "directory",
123+
"path": "/path/to/project/src",
124+
"children": [
125+
{
126+
"name": "Button.tsx",
127+
"type": "file",
128+
"path": "/path/to/project/src/Button.tsx",
129+
"size": 1024,
130+
"extension": "tsx"
131+
}
132+
]
133+
}
134+
]
135+
}
136+
```
137+
138+
**Markdown Format:**
139+
140+
```
141+
project/
142+
- src/
143+
- Button.tsx
144+
- Modal.tsx
145+
- index.ts
146+
- deno.json
147+
- README.md
148+
```
149+
69150
## Configuration
70151

71152
### Tree Options
@@ -79,6 +160,15 @@ interface TreeOptions {
79160
}
80161
```
81162

163+
### Generate Options
164+
165+
```ts
166+
interface GenerateOptions {
167+
format?: 'tree' | 'json' | 'markdown' // Output format (default: 'tree')
168+
includeStats?: boolean // Include file statistics (default: false)
169+
}
170+
```
171+
82172
### Performance Settings
83173

84174
```ts
@@ -134,6 +224,7 @@ const projectA = await tree.generate('/workspace/project-a')
134224
const projectB = await tree.generate('/workspace/project-b')
135225
const projectC = await tree.generate('/workspace/project-c')
136226

227+
// Log the trees
137228
console.log('Project A Tree:', projectA)
138229
console.log('Project B Tree:', projectB)
139230
console.log('Project C Tree:', projectC)
@@ -202,13 +293,13 @@ console.log('Project B:', treeB)
202293

203294
### Main API
204295

205-
| Method | Description | Parameters | Returns |
206-
| ------------ | -------------------------------------- | ------------------------------------- | ----------------- |
207-
| `clear()` | Clear all stored file metadata | None | `void` |
208-
| `generate()` | Generate formatted tree string | `rootPath: string` | `Promise<string>` |
209-
| `init()` | Scan directory and store file metadata | `path: string, options?: TreeOptions` | `Promise<void>` |
210-
| `remove()` | Remove file/directory from tree | `path: string` | `Promise<void>` |
211-
| `set()` | Add single file to tree metadata | `path: string` | `Promise<void>` |
296+
| Method | Description | Parameters | Returns |
297+
| ------------ | -------------------------------------- | --------------------------------------------- | ----------------- |
298+
| `clear()` | Clear all stored file metadata | None | `void` |
299+
| `generate()` | Generate formatted tree string | `rootPath: string, options?: GenerateOptions` | `Promise<string>` |
300+
| `init()` | Scan directory and store file metadata | `path: string, options?: TreeOptions` | `Promise<void>` |
301+
| `remove()` | Remove file/directory from tree | `path: string` | `Promise<void>` |
302+
| `set()` | Add single file to tree metadata | `path: string` | `Promise<void>` |
212303

213304
### Configuration Options
214305

@@ -219,6 +310,13 @@ console.log('Project B:', treeB)
219310
| `showHidden` | boolean || Show hidden files/directories | `false` |
220311
| `maxDepth` | number || Maximum directory depth | `3` |
221312

313+
### Generate Options
314+
315+
| Property | Type | Required | Description | Example |
316+
| -------------- | ------- | -------- | ----------------------- | ---------------------- |
317+
| `format` | string || Output format | `'json'`, `'markdown'` |
318+
| `includeStats` | boolean || Include file statistics | `true` |
319+
222320
### File Metadata
223321

224322
```ts
@@ -233,6 +331,20 @@ interface FileMetadata {
233331
}
234332
```
235333

334+
### Tree Node Structure
335+
336+
```ts
337+
interface TreeNode {
338+
name: string // Node name
339+
type: 'file' | 'directory' // Node type
340+
path: string // Absolute path
341+
size?: number // File size in bytes
342+
modified?: Date // Last modification date
343+
extension?: string // File extension
344+
children?: TreeNode[] // Child nodes
345+
}
346+
```
347+
236348
## Troubleshooting
237349

238350
### Common Issues
@@ -260,14 +372,21 @@ interface FileMetadata {
260372
```ts
261373
import tree from '@neabyte/deno-tree'
262374

263-
// Debug file storage
375+
// Debug file storage and generation
264376
await tree.init('/path/to/project', { maxFiles: 100 })
265-
console.log('Files stored:', tree.files.size)
266377

267378
// Debug specific path generation
268379
const result = await tree.generate('/path/to/project/src')
269380
console.log('Generated tree length:', result.length)
270381
console.log('Tree output:', result)
382+
383+
// Debug JSON format
384+
const jsonResult = await tree.generate('/path/to/project', { format: 'json' })
385+
console.log('JSON output length:', jsonResult.length)
386+
387+
// Debug markdown format
388+
const markdownResult = await tree.generate('/path/to/project', { format: 'markdown' })
389+
console.log('Markdown output length:', markdownResult.length)
271390
```
272391

273392
## Contributing

deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@neabyte/deno-tree",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"type": "module",
55
"license": "MIT",
66
"exports": "./src/index.ts",

src/Generator.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import type { FileMetadata, TreeNode } from '@app/Types.ts'
2+
import { formatDirectory } from '@app/Utils.ts'
3+
4+
/**
5+
* Tree output generator for different formats.
6+
* @description Handles generation of trees in various output formats.
7+
*/
8+
export class Generator {
9+
/**
10+
* Generates JSON format output.
11+
* @param rootPath - Root directory path
12+
* @param files - Map of scanned files
13+
* @returns JSON string
14+
*/
15+
async generateJsonFormat(rootPath: string, files: Map<string, FileMetadata>): Promise<string> {
16+
try {
17+
const treeNode = await this.buildTreeNode(rootPath, files)
18+
return JSON.stringify(treeNode, null, 2)
19+
} catch {
20+
return '{}'
21+
}
22+
}
23+
24+
/**
25+
* Generates Markdown format output.
26+
* @param rootPath - Root directory path
27+
* @param files - Map of scanned files
28+
* @returns Markdown string
29+
*/
30+
async generateMarkdownFormat(
31+
rootPath: string,
32+
files: Map<string, FileMetadata>
33+
): Promise<string> {
34+
try {
35+
const treeNode = await this.buildTreeNode(rootPath, files)
36+
return this.treeToMarkdown(treeNode)
37+
} catch {
38+
return ''
39+
}
40+
}
41+
42+
/**
43+
* Generates tree format output (original ASCII tree).
44+
* @param rootPath - Root directory path
45+
* @param files - Map of scanned files
46+
* @param showHidden - Whether to show hidden files
47+
* @returns ASCII tree string
48+
*/
49+
async generateTreeFormat(
50+
rootPath: string,
51+
files: Map<string, FileMetadata>,
52+
showHidden: boolean
53+
): Promise<string> {
54+
try {
55+
const allFiles = Array.from(files.values())
56+
const absoluteRootPath = await Deno.realPath(rootPath)
57+
const rootFiles = allFiles.filter(f => f.path.startsWith(`${absoluteRootPath}/`))
58+
if (rootFiles.length === 0) {
59+
return ''
60+
}
61+
const rootName = rootPath.split('/').pop() || rootPath
62+
return formatDirectory(rootName, absoluteRootPath, 0, allFiles, showHidden)
63+
} catch {
64+
return ''
65+
}
66+
}
67+
68+
/**
69+
* Builds node structure recursively.
70+
* @param name - Node name
71+
* @param path - Node path
72+
* @param files - All files in the tree
73+
* @returns TreeNode structure
74+
*/
75+
private buildNodeStructure(name: string, path: string, files: FileMetadata[]): TreeNode {
76+
const node: TreeNode = {
77+
name,
78+
type: 'directory',
79+
path,
80+
children: []
81+
}
82+
const children = files.filter(f => f.parent === path)
83+
const subdirs = new Set<string>()
84+
for (const file of children) {
85+
if (file.type === 'file') {
86+
const fileNode: TreeNode = {
87+
name: file.name,
88+
type: 'file',
89+
path: file.path
90+
}
91+
if (file.size !== undefined) {
92+
fileNode.size = file.size
93+
}
94+
if (file.modified !== undefined) {
95+
fileNode.modified = file.modified
96+
}
97+
if (file.extension !== undefined) {
98+
fileNode.extension = file.extension
99+
}
100+
if (node.children) {
101+
node.children.push(fileNode)
102+
}
103+
} else if (file.type === 'directory') {
104+
subdirs.add(file.path)
105+
}
106+
}
107+
for (const subdirPath of subdirs) {
108+
const subdirName = subdirPath.split('/').pop() || ''
109+
const subdirFiles = files.filter(f => f.path.startsWith(`${subdirPath}/`))
110+
const subdirNode = this.buildNodeStructure(subdirName, subdirPath, subdirFiles)
111+
if (node.children) {
112+
node.children.push(subdirNode)
113+
}
114+
}
115+
return node
116+
}
117+
118+
/**
119+
* Builds a TreeNode structure from scanned files.
120+
* @param rootPath - Root directory path
121+
* @param files - Map of scanned files
122+
* @returns TreeNode structure
123+
*/
124+
private async buildTreeNode(
125+
rootPath: string,
126+
files: Map<string, FileMetadata>
127+
): Promise<TreeNode> {
128+
const allFiles = Array.from(files.values())
129+
const absoluteRootPath = await Deno.realPath(rootPath)
130+
const rootFiles = allFiles.filter(f => f.path.startsWith(`${absoluteRootPath}/`))
131+
const rootName = rootPath.split('/').pop() || rootPath
132+
return this.buildNodeStructure(rootName, absoluteRootPath, rootFiles)
133+
}
134+
135+
/**
136+
* Converts TreeNode to Markdown format.
137+
* @param node - TreeNode to convert
138+
* @param depth - Current depth level
139+
* @returns Markdown string
140+
*/
141+
private treeToMarkdown(node: TreeNode, depth = 0): string {
142+
const indent = ' '.repeat(depth)
143+
const prefix = depth === 0 ? '' : '- '
144+
let result = `${indent}${prefix}${node.name}/\n`
145+
if (node.children) {
146+
for (const child of node.children) {
147+
if (child.type === 'directory') {
148+
result += this.treeToMarkdown(child, depth + 1)
149+
} else {
150+
const childIndent = ' '.repeat(depth + 1)
151+
result += `${childIndent}- ${child.name}\n`
152+
}
153+
}
154+
}
155+
return result
156+
}
157+
}

0 commit comments

Comments
 (0)