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 - 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"perlorpl— calls "indent_pl"xs— calls "indent_xs"xml,xsl,xslt,svg,xhtml— calls "indent_xml" in XML modehtml,htm,tmpl,tt,ep— calls "indent_xml" in HTML modecss,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
1for tabs. Typically set to2or4when using spaces. - indent_pp
-
Boolean. When true, indent C preprocessor directives according to their
#if/#endifnesting depth. Defaults to0(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.