NAME

Eshu - indentation fixer for C, Perl, XS, XML, HTML, CSS, JavaScript and POD source files

SYNOPSIS

use Eshu;

# Fix indentation for a specific language
my $fixed_c    = Eshu->indent_c($source);
my $fixed_pl   = Eshu->indent_pl($source);
my $fixed_xs   = Eshu->indent_xs($source);
my $fixed_xml  = Eshu->indent_xml($source);
my $fixed_html = Eshu->indent_html($source);
my $fixed_css  = Eshu->indent_css($source);
my $fixed_js   = Eshu->indent_js($source);
my $fixed_pod  = Eshu->indent_pod($source);

# Auto-dispatch by language name
my $fixed = Eshu->indent_string($source, lang => 'perl');

# Use spaces instead of tabs
my $fixed = Eshu->indent_c($source,
	indent_char  => ' ',
	indent_width => 4,
);

# Detect language from filename
my $lang = Eshu->detect_lang('lib/Foo.pm');  # 'perl'

# Fix a single file (read, detect, indent, optionally write back)
my $result = Eshu->indent_file('lib/Foo.pm', fix => 1);

# Fix an entire directory tree
my $report = Eshu->indent_dir('lib/', fix => 1);

DESCRIPTION

Eshu is an XS-powered indentation fixer that rewrites leading whitespace in C, Perl, XS, XML, HTML, CSS, JavaScript and POD source files. It tracks nesting depth and re-emits each line with correct indentation while leaving the content of each line untouched.

Eshu understands language-specific constructs that affect indentation:

C — strings, comments, preprocessor directives, block nesting
Perl — heredocs, regex, qw()/qq()/q(), s////tr////y///, pod sections, comments
XS — dual-mode scanning (C section above MODULE =, XS section below), XSUB boundaries, label detection (CODE:, OUTPUT:, BOOT:, etc.), C nesting within code body sections
XML — element nesting, self-closing tags, <!-- --> comments, <![CDATA[...]]> sections, processing instructions, multi-line tags
HTML — element nesting, void elements (br, hr, img, etc.), verbatim content in <script>, <style>, and <pre> blocks, comments
CSS — rule-block brace nesting, /* */ comments, string literals, url() tokens, at-rules (@media, @keyframes, etc.)
JavaScript — brace/paren/bracket nesting, double and single-quoted strings, template literals (`...${expr}...`) with nested interpolation, regex literals (/pattern/flags), line and block comments. <script> blocks in HTML are automatically indented as JS.
POD — directive lines (=head1, =over, =item, =back, =cut, etc.) normalised to column 0, text paragraphs at column 0, code examples (whitespace-leading lines) normalised to one indent level. POD sections embedded in Perl files are automatically processed.

The engine is written in C for speed and operates as a single-pass line-by-line scanner.

The Eshu distribution also includes a command-line tool (eshu) that can fix files in-place, preview changes as a diff, or run in CI check mode. It supports recursive directory processing with file inclusion/exclusion patterns and automatic language detection by extension. There is also a vim plugin (eshu.vim) that can fix the open file or a selected 'visual' range.

METHODS

indent_c

my $out = Eshu->indent_c($source, %opts);

Fix indentation of C source code. Tracks {}, (), [] nesting, handles C strings, character literals, line and block comments, and preprocessor directives.

indent_pl

my $out = Eshu->indent_pl($source, %opts);

Fix indentation of Perl source code. In addition to brace nesting, handles heredocs (<<EOF, <<~EOF, <<'EOF', <<"EOF"), regex literals, qw()/qq()/q() constructs with paired and non-paired delimiters, multi-section operators (s///, tr///, y///), pod sections (=head1 through =cut), and line comments.

indent_xs

my $out = Eshu->indent_xs($source, %opts);

Fix indentation of XS source files. Operates in dual mode: lines above the first MODULE = ... line are processed as C; lines below are processed as XS with XSUB boundary detection, label recognition, and C nesting tracking within code body sections. BOOT: sections use a shallower indentation depth than other labels.

indent_xml

my $out = Eshu->indent_xml($source, %opts);

Fix indentation of XML source. Tracks element nesting via opening and closing tags, handles self-closing tags (<br/>), comments (<!-- -->), CDATA sections, and processing instructions (<?...?>). Multi-line tags are indented one level beyond the opening tag.

Also used for HTML when called via indent_string with lang => 'html'. In HTML mode, void elements (br, hr, img, input, meta, link, etc.) are treated as self-closing, and content inside <script>, <style>, and <pre> blocks is passed through verbatim. <script> blocks are indented with the JavaScript engine.

indent_html

my $out = Eshu->indent_html($source, %opts);

Fix indentation of HTML source. This is a convenience method equivalent to calling Eshu->indent_xml($source, lang => 'html', %opts). Void elements (br, hr, img, input, meta, link, etc.) are treated as self-closing. <script> blocks are indented with the JavaScript engine, while <style> and <pre> content is passed through verbatim.

indent_css

my $out = Eshu->indent_css($source, %opts);

Fix indentation of CSS, SCSS, and LESS source. Tracks brace nesting for rule blocks and at-rules (@media, @keyframes, @supports, etc.), handles /* */ block comments, string literals (single and double-quoted), and url() tokens with unquoted paths.

indent_js

my $out = Eshu->indent_js($source, %opts);

Fix indentation of JavaScript (and TypeScript) source. Tracks {}, (), [] nesting, handles double-quoted strings, single-quoted strings, template literals (backtick strings with ${expr} interpolation), regex literals (/pattern/flags) with character class support, line comments (//) and block comments (/* */). Multi-line template literal content is preserved verbatim (whitespace is significant).

indent_pod

my $out = Eshu->indent_pod($source, %opts);

Fix indentation of POD (Plain Old Documentation). Directive lines (=head1, =head2, =over, =item, =back, =cut, =pod, =begin, =end, =for, =encoding) are normalised to column 0. Text paragraphs stay at column 0. Code examples (lines with leading whitespace) are normalised to one indent level. POD sections embedded in Perl source files are automatically processed by the Perl engine.

indent_string

my $out = Eshu->indent_string($source, lang => $lang, %opts);

Dispatch to the appropriate engine based on the lang parameter:

c (default) — calls "indent_c"
perl or pl — calls "indent_pl"
xs — calls "indent_xs"
xml, xsl, xslt, svg, xhtml — calls "indent_xml" in XML mode
html, htm, tmpl, tt, ep — calls "indent_xml" in HTML mode
css, scss, less — calls "indent_css"
js, javascript, jsx, ts, typescript, tsx, mjs, cjs, mts — calls "indent_js"
pod — calls "indent_pod"

detect_lang

my $lang = Eshu->detect_lang($filename);

Return a language string based on the file extension, suitable for passing to "indent_string". Returns undef for unrecognised extensions.

.c, .h                       → 'c'
.xs                          → 'xs'
.pl, .pm, .t                 → 'perl'
.xml, .xsl, .xslt, .svg     → 'xml'
.xhtml                       → 'xhtml'
.html, .htm, .tmpl, .tt, .ep → 'html'
.css, .scss, .less           → 'css'
.js, .jsx, .mjs, .cjs       → 'js'
.ts, .tsx, .mts              → 'js'
.pod                         → 'pod'

indent_file

my $result = Eshu->indent_file($path, %opts);

Read a single file, detect its language, run the indentation fixer, and optionally write the result back. Returns a hashref:

{
	file   => $path,
	status => 'changed',      # or 'unchanged', 'needs_fixing',
				   #    'skipped', 'error'
	lang   => 'perl',
	diff   => '...',           # only if diff => 1
	reason => '...',           # only if status is 'skipped'
	error  => '...',           # only if status is 'error'
}

Files are skipped if they are over 1 MB, contain NUL bytes in the first 8 KB (binary detection), or have an unrecognised extension.

Options: all "OPTIONS" keys plus fix, diff, and lang.

indent_dir

my $report = Eshu->indent_dir($path, %opts);

Recursively walk a directory, detect languages by extension, and fix indentation for all recognised files. Returns a report hashref:

{
	files_checked => 42,
	files_changed => 7,
	files_skipped => 3,
	files_errored => 0,
	changes       => [ ... ],   # array of indent_file results
}

Options: all "OPTIONS" keys plus:

fix — write changes back to disk (default: dry-run)
diff — include diff output in each result
recursive — recurse into subdirectories (default: 1)
exclude — regexp or arrayref of regexps; skip files matching any
include — regexp or arrayref of regexps; only process files matching at least one
lang — force a language for all files instead of auto-detecting

Symlinks to files are followed; symlinks to directories are not (to avoid cycles).

OPTIONS

All indentation methods accept the following options as key-value pairs:

indent_char

Character to use for indentation. Either "\t" (tab, the default) or " " (space).

indent_width

Number of characters per indentation level. Defaults to 1 for tabs. Typically set to 2 or 4 when using spaces.

indent_pp

Boolean. When true, indent C preprocessor directives according to their #if/#endif nesting depth. Defaults to 0 (preprocessor directives stay at column 0). Only meaningful for C and XS engines.

CLI

Eshu ships with a command-line tool:

# Fix a file in-place
eshu --fix lib/Foo.pm

# Preview changes as a diff
eshu --diff lib/Foo.pm

# CI check — exit 1 if file would change
eshu --check lib/Foo.pm

# Read stdin, write stdout
cat messy.c | eshu --lang c

# Use 4-space indentation
eshu --spaces 4 --fix src/bar.c

# Fix all recognised files in a directory tree
eshu --fix lib/

# Fix only Perl files, excluding backups
eshu --fix --include '\.pm$' --exclude '\.bak$' lib/

# Verbose output showing every file processed
eshu --fix --verbose lib/

Run eshu --help for the full list of options.

AUTHOR

LNATION <email@lnation.org>

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.