@herb-tools/formatter 0.4.0 → 0.4.2

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.
@@ -11,6 +11,8 @@ export declare class Printer extends Visitor {
11
11
  private source;
12
12
  private lines;
13
13
  private indentLevel;
14
+ private inlineMode;
15
+ private isInComplexNesting;
14
16
  constructor(source: string, options: Required<FormatOptions>);
15
17
  print(object: Node | Token, indentLevel?: number): string;
16
18
  private push;
@@ -52,4 +54,17 @@ export declare class Printer extends Visitor {
52
54
  private visitERBGeneric;
53
55
  private renderInlineOpen;
54
56
  renderAttribute(attribute: HTMLAttributeNode): string;
57
+ /**
58
+ * Try to render children inline if they are simple enough.
59
+ * Returns the inline string if possible, null otherwise.
60
+ */
61
+ private tryRenderInline;
62
+ /**
63
+ * Calculate the maximum nesting depth in a subtree of nodes.
64
+ */
65
+ private getMaxNestingDepth;
66
+ /**
67
+ * Render an HTML element's content inline (without the wrapping tags).
68
+ */
69
+ private renderElementInline;
55
70
  }
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "@herb-tools/formatter",
3
- "version": "0.4.0",
4
- "type": "module",
3
+ "version": "0.4.2",
5
4
  "license": "MIT",
6
5
  "homepage": "https://herb-tools.dev",
7
6
  "bugs": "https://github.com/marcoroth/herb/issues/new?title=Package%20%60@herb-tools/formatter%60:%20",
@@ -15,7 +14,7 @@
15
14
  "require": "./dist/index.cjs",
16
15
  "types": "./dist/types/index.d.ts",
17
16
  "bin": {
18
- "herb-formatter": "bin/herb-formatter"
17
+ "herb-format": "bin/herb-format"
19
18
  },
20
19
  "scripts": {
21
20
  "build": "yarn clean && rollup -c rollup.config.mjs",
@@ -35,7 +34,8 @@
35
34
  }
36
35
  },
37
36
  "dependencies": {
38
- "@herb-tools/core": "0.4.0"
37
+ "@herb-tools/core": "0.4.2",
38
+ "glob": "^11.0.3"
39
39
  },
40
40
  "files": [
41
41
  "package.json",
package/src/cli.ts CHANGED
@@ -1,30 +1,45 @@
1
- import { readFileSync } from "fs"
1
+ import dedent from "dedent"
2
+ import { readFileSync, writeFileSync, statSync } from "fs"
3
+ import { glob } from "glob"
4
+ import { join, resolve } from "path"
5
+
2
6
  import { Herb } from "@herb-tools/node-wasm"
3
7
  import { Formatter } from "./formatter.js"
8
+
4
9
  import { name, version } from "../package.json"
5
10
 
11
+ const pluralize = (count: number, singular: string, plural: string = singular + 's'): string => {
12
+ return count === 1 ? singular : plural
13
+ }
14
+
6
15
  export class CLI {
7
- private usage = `
8
- Usage: herb-formatter [file] [options]
16
+ private usage = dedent`
17
+ Usage: herb-format [file|directory] [options]
9
18
 
10
- Arguments:
11
- file File to format (use '-' or omit for stdin)
19
+ Arguments:
20
+ file|directory File to format, directory to format all **/*.html.erb files within,
21
+ or '-' for stdin (omit to format all **/*.html.erb files in current directory)
12
22
 
13
- Options:
14
- -h, --help show help
15
- -v, --version show version
23
+ Options:
24
+ -c, --check check if files are formatted without modifying them
25
+ -h, --help show help
26
+ -v, --version show version
16
27
 
17
- Examples:
18
- herb-formatter templates/index.html.erb
19
- cat template.html.erb | herb-formatter
20
- herb-formatter - < template.html.erb
21
- `
28
+ Examples:
29
+ herb-format # Format all **/*.html.erb files in current directory
30
+ herb-format --check # Check if all **/*.html.erb files are formatted
31
+ herb-format templates/index.html.erb # Format and write single file
32
+ herb-format --check templates/ # Check if all **/*.html.erb files in templates/ are formatted
33
+ cat template.html.erb | herb-format # Format from stdin to stdout
34
+ herb-format - < template.html.erb # Format from stdin to stdout
35
+ `
22
36
 
23
37
  async run() {
24
38
  const args = process.argv.slice(2)
25
39
 
26
40
  if (args.includes("--help") || args.includes("-h")) {
27
41
  console.log(this.usage)
42
+
28
43
  process.exit(0)
29
44
  }
30
45
 
@@ -34,26 +49,163 @@ export class CLI {
34
49
  if (args.includes("--version") || args.includes("-v")) {
35
50
  console.log("Versions:")
36
51
  console.log(` ${name}@${version}, ${Herb.version}`.split(", ").join("\n "))
52
+
37
53
  process.exit(0)
38
54
  }
39
55
 
40
- let source: string
56
+ console.log("⚠️ Experimental Preview: The formatter is in early development. Please report any unexpected behavior or bugs to https://github.com/marcoroth/herb/issues")
57
+ console.log()
58
+
59
+ const formatter = new Formatter(Herb)
60
+ const isCheckMode = args.includes("--check") || args.includes("-c")
41
61
 
42
- // Find the first non-flag argument (the file)
43
62
  const file = args.find(arg => !arg.startsWith("-"))
44
63
 
45
- // Read from file or stdin
46
- if (file && file !== "-") {
47
- source = readFileSync(file, "utf-8")
64
+ if (!file && !process.stdin.isTTY) {
65
+ if (isCheckMode) {
66
+ console.error("Error: --check mode is not supported with stdin")
67
+
68
+ process.exit(1)
69
+ }
70
+
71
+ const source = await this.readStdin()
72
+ const result = formatter.format(source)
73
+ const output = result.endsWith('\n') ? result : result + '\n'
74
+
75
+ process.stdout.write(output)
76
+ } else if (file === "-") {
77
+ if (isCheckMode) {
78
+ console.error("Error: --check mode is not supported with stdin")
79
+
80
+ process.exit(1)
81
+ }
82
+
83
+ const source = await this.readStdin()
84
+ const result = formatter.format(source)
85
+ const output = result.endsWith('\n') ? result : result + '\n'
86
+
87
+ process.stdout.write(output)
88
+ } else if (file) {
89
+ try {
90
+ const stats = statSync(file)
91
+
92
+ if (stats.isDirectory()) {
93
+ const pattern = join(file, "**/*.html.erb")
94
+ const files = await glob(pattern)
95
+
96
+ if (files.length === 0) {
97
+ console.log(`No files found matching pattern: ${resolve(pattern)}`)
98
+ process.exit(0)
99
+ }
100
+
101
+ let formattedCount = 0
102
+ let unformattedFiles: string[] = []
103
+
104
+ for (const filePath of files) {
105
+ try {
106
+ const source = readFileSync(filePath, "utf-8")
107
+ const result = formatter.format(source)
108
+ const output = result.endsWith('\n') ? result : result + '\n'
109
+
110
+ if (output !== source) {
111
+ if (isCheckMode) {
112
+ unformattedFiles.push(filePath)
113
+ } else {
114
+ writeFileSync(filePath, output, "utf-8")
115
+ console.log(`Formatted: ${filePath}`)
116
+ }
117
+
118
+ formattedCount++
119
+ }
120
+ } catch (error) {
121
+ console.error(`Error formatting ${filePath}:`, error)
122
+ }
123
+ }
124
+
125
+ if (isCheckMode) {
126
+ if (unformattedFiles.length > 0) {
127
+ console.log(`\nThe following ${pluralize(unformattedFiles.length, 'file is', 'files are')} not formatted:`)
128
+ unformattedFiles.forEach(file => console.log(` ${file}`))
129
+ console.log(`\nChecked ${files.length} ${pluralize(files.length, 'file')}, found ${unformattedFiles.length} unformatted ${pluralize(unformattedFiles.length, 'file')}`)
130
+ process.exit(1)
131
+ } else {
132
+ console.log(`\nChecked ${files.length} ${pluralize(files.length, 'file')}, all files are properly formatted`)
133
+ }
134
+ } else {
135
+ console.log(`\nChecked ${files.length} ${pluralize(files.length, 'file')}, formatted ${formattedCount} ${pluralize(formattedCount, 'file')}`)
136
+ }
137
+ } else {
138
+ const source = readFileSync(file, "utf-8")
139
+ const result = formatter.format(source)
140
+ const output = result.endsWith('\n') ? result : result + '\n'
141
+
142
+ if (output !== source) {
143
+ if (isCheckMode) {
144
+ console.log(`File is not formatted: ${file}`)
145
+ process.exit(1)
146
+ } else {
147
+ writeFileSync(file, output, "utf-8")
148
+ console.log(`Formatted: ${file}`)
149
+ }
150
+ } else if (isCheckMode) {
151
+ console.log(`File is properly formatted: ${file}`)
152
+ }
153
+ }
154
+
155
+ } catch (error) {
156
+ console.error(`Error: Cannot access '${file}':`, error)
157
+
158
+ process.exit(1)
159
+ }
48
160
  } else {
49
- source = await this.readStdin()
50
- }
161
+ const files = await glob("**/*.html.erb")
51
162
 
52
- const formatter = new Formatter(Herb)
53
- const result = formatter.format(source)
54
- process.stdout.write(result)
163
+ if (files.length === 0) {
164
+ console.log(`No files found matching pattern: ${resolve("**/*.html.erb")}`)
165
+
166
+ process.exit(0)
167
+ }
168
+
169
+ let formattedCount = 0
170
+ let unformattedFiles: string[] = []
171
+
172
+ for (const filePath of files) {
173
+ try {
174
+ const source = readFileSync(filePath, "utf-8")
175
+ const result = formatter.format(source)
176
+ const output = result.endsWith('\n') ? result : result + '\n'
177
+
178
+ if (output !== source) {
179
+ if (isCheckMode) {
180
+ unformattedFiles.push(filePath)
181
+ } else {
182
+ writeFileSync(filePath, output, "utf-8")
183
+ console.log(`Formatted: ${filePath}`)
184
+ }
185
+ formattedCount++
186
+ }
187
+ } catch (error) {
188
+ console.error(`Error formatting ${filePath}:`, error)
189
+ }
190
+ }
191
+
192
+ if (isCheckMode) {
193
+ if (unformattedFiles.length > 0) {
194
+ console.log(`\nThe following ${pluralize(unformattedFiles.length, 'file is', 'files are')} not formatted:`)
195
+ unformattedFiles.forEach(file => console.log(` ${file}`))
196
+ console.log(`\nChecked ${files.length} ${pluralize(files.length, 'file')}, found ${unformattedFiles.length} unformatted ${pluralize(unformattedFiles.length, 'file')}`)
197
+
198
+ process.exit(1)
199
+ } else {
200
+ console.log(`\nChecked ${files.length} ${pluralize(files.length, 'file')}, all files are properly formatted`)
201
+ }
202
+ } else {
203
+ console.log(`\nChecked ${files.length} ${pluralize(files.length, 'file')}, formatted ${formattedCount} ${pluralize(formattedCount, 'file')}`)
204
+ }
205
+ }
55
206
  } catch (error) {
56
207
  console.error(error)
208
+
57
209
  process.exit(1)
58
210
  }
59
211
  }