PERLIMPORTS(1p)
π NAME β¨
okay so bestie, meet perlimports β A command line utility for cleaning up imports in your Perl code. that's it, that's the whole vibe. your use statements are about to get a glow-up π
πΈ VERSION
version 0.000060
π SYNOPSIS
okay so first things first, you're gonna make a config file called perlimports.toml and park it at the top level of your application or repo:
perlimports --create-config-file perlimports.toml
you can also do the whole config-file-with-a-leading-dot thing, lowkey iconic:
perlimports --create-config-file .perlimports.toml
and for system-wide defaults, drop a file in $XDG_HOME. something like this, bestie:
perlimports --create-config-file ~/.config/perlimports/perlimports.toml
once you've got that config file set up exactly how you like it, you can literally just stop using most of the command line switches β the only ones you still need are -i, --lint or --read-stdin. periodt.
now let's update a file in place. π«Ά
bestie please make sure you can revert the file if you need to, because perlimports will not make a backup. no cap. it just edits. π
perlimports -i foo.pl
lint a file? sure:
perlimports --lint foo.pl
in-place edits on directories:
perlimports -i lib t xt
lint directories:
perlimports --lint lib t xt
in-place edits on files and directories:
perlimports -i foo.pl lib t xt
run perlimports on a file and only print the results to STDOUT:
perlimports foo.pl
if some of your imported modules live in local directories, you can give it some hints about where to find them:
perlimports -i --libs t/lib,/some/dir/lib foo.pl
redirect output to a new file, go off:
perlimports foo.pl > foo.new.pl
π DESCRIPTION
so this distribution ships the perlimports command line interface (CLI), and what it does is automate the cleanup and maintenance of your Perl use and require statements. it's loosely inspired by goimports (https://pkg.go.dev/golang.org/x/tools/cmd/goimports), and the whole goal is for it to become part of your linting and tidying workflow β same way you'd use perltidy or perlcritic. you get it. β¨
if you want the detailed deep-dive on the exact problems this tool is trying to solve, go watch this "Conference in the Cloud" talk from June 2021: Where did that Symbol Come From? (https://www.youtube.com/watch?v=fKqxdTbGxYY).
the slides for that talk are also available, and yes the command is exactly this:
curl -O https://raw.githubusercontent.com/oalders/presentations/main/slides/6-perlimports/remark.html && open remark.html
π MOTIVATION
so like, lots of Perl modules helpfully export functions and variables by default. those are super handy shortcuts when you're writing a quick or small script, but the way code grows organically? they become a maintenance burden fr fr. and when code gets more complex, that complexity costs you in development time. flip side: reducing code complexity can speed development up. this tool wants to reduce complexity to further that goal. that's the whole point bestie. π
importing symbols by default or using export tags is a convenient shorthand for getting work done, but it means the developer has to retain knowledge of those defaults and tags in order to actually understand the code. perlimports wants you to be able to develop your code however you see fit, while still giving you a viable option to tidy your imports automatically. same energy as using perltidy to format your code β except now you're automating the "make my imports easier to understand" part. let's look at some examples. π₯
π¦ Where is this function defined?
you might run into some code like this:
use strict;
use warnings;
use HTTP::Request::Common;
use LWP::UserAgent;
my $ua = LWP::UserAgent->new;
my $req = $ua->request( GET 'https://metacpan.org/' );
print $req->content;
so where does GET even come from?? if you're not familiar with HTTP::Request::Common, you might not realize that use HTTP::Request::Common has implicitly imported the functions GET, HEAD, PUT, PATCH, POST and OPTIONS into this block of code. sneaky.
what would happen if we used perlimports to import all needed functions explicitly? it might look something like this:
use strict;
use warnings;
use HTTP::Request::Common qw( GET );
use LWP::UserAgent ();
my $ua = LWP::UserAgent->new;
my $req = $ua->request( GET 'https://metacpan.org/' );
print $req->content;
the code above makes it immediately obvious where GET originates, which in turn makes it easier for us to look up its documentation. added bonus: it's also not importing HEAD, PUT or any of the other functions HTTP::Request::Common exports by default. so those functions literally cannot be used unwittingly later in the code. more understandable code for present day you, future you, and anyone else tasked with reading your code at some future point. such a slay. π
keep in mind that this simple act can save a lot of time for developers who aren't intimately familiar with Perl and the default exports of many CPAN modules.
πΈ Are we even using all of these imports?
imagine the following import statement:
use HTTP::Status qw(
is_cacheable_by_default
is_client_error
is_error
is_info
is_redirect
is_server_error
is_success
status_message
);
...followed by 3,000 lines of code. like. how do you even know if all of these functions are actually being used? were they ever used? you could grep all of these function names manually, or you could remove them one by one by trial and error to see what breaks. it's doable, sure, but it does not scale well to scripts and modules with many imports, or to large code bases with many imports. having an unmaintained list of imports is preferable to implicit imports, ngl β but it would be helpful to automate maintaining this list.
perlimports can, in many situations, clean up your import statements and automate this maintenance burden away. which makes it easier for you to write clean code, which is easier to understand. obsessed. β¨
π Are we even using all of these modules?
in cases where code is implicitly importing from modules, or where explicit imports are not being curated, it can be hard to discover which modules are no longer being used in a script, a module, or even a whole code base. removing unused modules from code can lead to gains in performance and a decrease in consumption of resources. and removing entire modules from your code base can decrease the number of dependencies you need to manage and decrease friction in your deployment process.
perlimports can remove unused modules for you, making dependency management so much easier. π«Ά
π Enforcing a consistent style
a messy list of module imports makes your code harder to read. imagine this:
use Cpanel::JSON::XS;
use Database::Migrator::Types qw( HashRef ArrayRef Object Str Bool Maybe CodeRef FileHandle RegexpRef );
use List::AllUtils qw( uniq any );
use LWP::UserAgent q{};
use Try::Tiny qw/ catch try /;
use WWW::Mechanize q<>;
perlimports turns the above list into:
use Cpanel::JSON::XS ();
use Database::Migrator::Types qw(
ArrayRef
Bool
CodeRef
FileHandle
HashRef
Maybe
Object
RegexpRef
Str
);
use List::AllUtils qw( any uniq );
use LWP::UserAgent ();
use Try::Tiny qw( catch try);
use WWW::Mechanize ();
where possible, perlimports will enforce a consistent style of parentheses, and it'll also sort your imports and break up long lines. and like mentioned above, if some imports are no longer in use, perlimports will helpfully remove those for you. iconic behaviour, honestly. π
π§ Import tags
import tags may obscure where symbols are coming from. while import tags provide a useful shorthand, they can contribute to code complexity by obscuring the origin of imported symbols. consider:
use HTTP::Status qw(:constants :is status_message);
the line above imports the status_message() function, as well as some other things via :constants and :is. but bestie... what exactly are these things?? we'll need to read the documentation to know for sure. π
perlimports can audit your code and expand the line above to list the symbols which you are actually importing. so the line above might now look something like:
use HTTP::Status qw(
HTTP_ACCEPTED
HTTP_BAD_REQUEST
HTTP_CONTINUE
HTTP_I_AM_A_TEAPOT
HTTP_MOVED_PERMANENTLY
HTTP_NO_CODE
HTTP_NOT_FOUND
HTTP_OK
HTTP_PAYLOAD_TOO_LARGE
HTTP_PERMANENT_REDIRECT
HTTP_RANGE_NOT_SATISFIABLE
HTTP_REQUEST_ENTITY_TOO_LARGE
HTTP_REQUEST_RANGE_NOT_SATISFIABLE
HTTP_REQUEST_URI_TOO_LARGE
HTTP_TOO_EARLY
HTTP_UNORDERED_COLLECTION
HTTP_URI_TOO_LONG
is_cacheable_by_default
is_client_error
is_error
is_info
is_redirect
is_server_error
is_success
status_message
);
this is more verbose, yes, but grepping your code will now reveal to you where something like is_cacheable_by_default gets defined. you have increased the lines of code, but you have also reduced complexity. a trade bestie would make every single time. β
π COMMAND LINE PARAMETERS π
okay so here's the whole menu of knobs you can twiddle, bestie. buckle up β¨
πΈ --create-config-file
This one wants a path to a .toml file that doesn't exist yet (it's gonna make it for you, no cap π).
--create-config-file perlimports.toml
--create-config-file .perlimports.toml
and if you want defaults for the whole system, you can drop one in $XDG_HOME, something like:
perlimports --create-config-file ~/.config/perlimports/perlimports.toml
every single one of those paths is a default location perlimports will go hunting for β unless you handed it a --config-file argument, in which case it's that one, periodt. π
π¦ --config-file
path to a perlimports config file, bestie. if you don't pass this, we go looking for a file named perlimports.toml or .perlimports.toml in the current directory, and after that we look for perlimports.toml inside XDG_CONFIG_HOME (usually something like $HOME/perlimports/perlimports.toml). and ngl if you want this whole autopilot thing OFF, that's what the next flag is for π
π --no-config-file
stops perlimports from automatically grabbing a config file from any of the standard locations. use this when you want literally all of your behaviour defined on the command line, main character style. β¨
π --filename|-f
absolute-or-relative path to a file (or a directory) to process.
--filename path/to/file
-f path/to/file
-f path/to/dir
lowkey important: if you don't give a --filename, we fall back to checking @ARGV for leftover args. so this:
perlimports --filename path/to/file
is the same thing as this:
perlimports path/to/file
and you can absolutely hand us a whole pile of file and dir names, we eat those for breakfast:
perlimports path/to/file path/to/other/file lib t xt
π --ignore-modules
a comma-separated list of module names this script should just... not touch. anything in this list stays exactly the same after processing. no edits, no nothing.
--ignore-modules Foo,Foo::Bar
π --ignore-modules-filename
absolute-or-relative path to a file with a list of module names to ignore (same behaviour as above). the pattern is one module name per line.
Foo
Foo::Bar
π --ignore-modules-pattern
a regex that matches module names that should be ignored by this script. anything the regex catches stays unchanged after processing.
--ignore-modules-pattern '^(Foo|Foo::Bar)'
β --ignore-modules-pattern-filename
absolute-or-relative path to a file containing a list of regexes that match modules to ignore (same behaviour as above, obviously π ). the pattern is one regular expression per line.
^Foo
^Foo::Bar
π --never-export-modules
comma-separated list of module names that should never export symbols. if we find them, we make sure they have an empty import list. so use Foo; becomes use Foo ();. iconic behaviour fr fr.
--never-export-modules Foo,Foo::Bar
π --never-export-modules-filename
absolute-or-relative path to a file with a list of module names that should never export symbols (behaviour same as above, you know the drill). pattern = one module name per line.
Foo
Foo::Bar
π₯ --inplace-edit|-i
edit the file in place instead of printing the result to STDOUT. make sure you have a backup copy first!!
--inplace--edit
-i
β οΈ bestie I'm gonna say it twice because the manpage says it twice: this edits the file in place rather than printing the result to STDOUT. make sure you have a backup copy first.
π§ --json
(Experimental). if you flip this on, linting errors get emitted as JSON objects, one object per error. it's meant to make life easier for editors trying to parse line numbers, column numbers and the error message. this flag can only be used in tandem with --lint.
πΈ --lint
pass this argument and perlimports switches into linter mode instead of tidier mode. failure messages (and success messages) get reported on STDERR. failures also return a non-zero exit code.
this is still a bit experimental, so the output format and the exit codes could change in a subsequent release. π
it also cannot be combined with tidying. so passing --lint --inplace-edit will generate an error. pick a lane, babe.
π
--indent
sets the indent width, in spaces, for qw() import lists that get split across multiple lines. it defaults to the value of perltidy's indent-columns setting (read from .perltidyrc if that file is present), or 4 if it's unset.
# --indent 2
use Foo qw(
bar
baz
);
π --[no-]pad-brackets
when enabled, it adds whitespace inside the square brackets of test builder import lists. defaults to disabled.
# --pad-brackets
use Test::More import => [ qw( ok ) ];
# --no-pad-brackets
use Test::More import => [qw( ok )];
β¨ --[no-]padding
--padding is enabled by default, so you only need to pass this arg if you want to be extra explicit about it. this setting adds whitespace inside the parentheses.
# --padding
use Foo qw( bar baz );
and the --no-padding arg lets you turn the extra padding inside parentheses off:
# --no-padding
use Foo qw(bar baz);
π¦ --[no-]tidy-whitespace
--tidy-whitespace is enabled by default. what that means is: use statements get updated even when the only change is whitespace. turning it off can help cut down the churn when you run perlimports, especially if your codebase doesn't have automated tidying going on.
and listen β if you switched from --padding to --no-padding (or the other way around), you'll probably wanna make sure --tidy-whitespace is enabled too, so you can actually see those whitespace changes. π
π --libs
a comma-separated list of module directories that aren't in your @INC.
--libs lib,t/lib
π --[no-]preserve-duplicates
when enabled, only one use statement per module gets preserved. it defaults to preserving duplicate statements.
so for example, when enabled, this text:
use Foo qw( bar );
use Foo qw (baz );
gets converted to:
use Foo qw( bar baz );
and if it's left disabled, the same thing above will probably get converted to:
use Foo qw( bar baz );
use Foo qw( bar baz );
which lets you manually decide how you actually want to handle the imports in question. use this setting with care, bestie π«Ά
π --[no-]preserve-unused
when enabled, unused modules get removed. it defaults to preserving unused modules.
β οΈ enabling this may remove modules that are only there for preloading purposes, or that aren't being detected for other reasons. so yeah β use this setting with care.
π --range-begin
Experimental. first line of a range to tidy or lint. the line ranges begin at 1 (not 0). this will select the appropriate range of lines from STDIN. requires --read-stdin. mostly useful for editors. unless you're writing an editor plugin or extension, you can probably just ignore this one. π
β --range-end
Experimental. last line of a range to tidy or lint. line ranges begin at 1 (not 0). this selects the appropriate range of lines from STDIN. requires --read-stdin. mostly useful for editors. and again β unless you're building an editor plugin or extension, you can probably ignore this. β¨
π --read-stdin
read statements to process from STDIN instead of processing the entire file. this is built for use by editors, like vim. check out the INTEGRATIONS section below for more on setting up an editor integration.
if this option is enabled, then --inplace-edit|-i is not available. sorry babe.
--read-stdin
π§ --log-level|-l
generally only useful for debugging. notice notifies you about progress, like which file or snippet is currently being processed. info will generally log the errors that got swallowed while text was being processed. all levels are subject to change.
--log-level notice
--log-level info
-l notice
-l info
see <https://metacpan.org/pod/Log::Dispatch#LOG-LEVELS> for a list of available log levels. log output defaults to STDERR. check --log-filename if you'd rather log to a file instead.
π --log-filename
name of a file to redirect logs to, instead of STDERR.
πΈ --help
outputs a concise help menu with a summary of the available parameters.
--help
π --verbose-help
includes the SYNOPSIS section from this page after printing the --help menu listed above. so it's the help menu plus bonus content. slay. β¨
π ANNOTATIONS/IGNORING MODULES π
so besides all the documented command line switches for ignoring modules, you can also just drop annotations right in your code. iykyk. π«Ά
use Encode; ## no perlimports
that tells perlimports not to attempt a tidy of this line.
## no perlimports
use Encode;
use Cpanel::JSON::XS;
## use perlimports
use POSIX ();
and that tells perlimports not to tidy the two modules sitting inside the annotations.
β οΈ important caveat though: since perlimports needs to know as much as possible about what's going on in a file, these annotations don't prevent modules from being loaded. it's only a directive to leave the lines in the file unchanged after processing. got it? good π
π INTEGRATIONS π
Okay bestie, listen β you are encouraged (like, heavily encouraged, the author is being so nice about it) to make this tool part of your automated tidying workflow. Here's some guidance on how to set that up. π«Ά
β¨ VIM β¨
If you're a vim girlie, you can pipe your import statements straight into perlimports. Look how easy:
:vnoremap <silent> im :!perlimports --read-stdin --filename '%:p'<CR>
So what that little guy does is let you visually select one or more lines of code and have them updated in place by perlimports. Once you've got the code selected, you hit im and boom π
β your imports get (re)formatted. Iconic.
πΈ VIM and ALE πΈ
If you use ALE with vim, you can drop something like this into your vim config. Heads up though: this function will save your buffer before running perlimports. No sneaky unsaved changes, bestie.
function! Perlimports(buffer) abort
write
return {
\ 'command': 'perlimports --read-stdin -f %s'
\}
endfunction
let ale_fixers.perl = ['perlimports', 'perltidy']
execute ale#fix#registry#Add('perlimports', 'Perlimports', ['perl'], 'Tidy Perl imports')
π¦ Emacs and Flycheck π¦
Flycheck https://www.flycheck.org/ 35 and newer will just... Suggest changes from perlimports automatically if it finds that it's installed. No config, no drama. It's giving main character energy fr fr. β¨
π precious π
If you're a https://github.com/houseabsolute/precious user, your config might look something like this:
exclude = [
# Used by Dist::Zilla
".build",
"App-perlimports-*",
"blib",
"inc",
"test-data",
# All of these are generated by Dist::Zilla
"t/00-*",
"t/author-*",
"t/release-*",
"xt/author",
"xt/release",
]
[commands.perlimports]
type = "both"
include = [ "**/*.{pl,pm,t,psgi}" ]
cmd = [ "perlimports" ]
lint_flags = [ "--lint"]
tidy_flags = [ "-i"]
ok_exit_codes = 0
expect_stderr = true
[commands.perltidy]
type = "both"
include = [ "**/*.{pl,pm,t,psgi}" ]
cmd = [ "perltidy", "--profile=$PRECIOUS_ROOT/perltidyrc" ]
lint_flags = [ "--assert-tidy", "--no-standard-output", "--outfile=/dev/null" ]
tidy_flags = [ "--backup-and-modify-in-place", "--backup-file-extension=/" ]
ok_exit_codes = 0
lint_failure_exit_codes = 2
expect_stderr = true
So here's the tea: https://github.com/houseabsolute/precious runs plugins in order, which is why we put the perltidy config after perlimports. That's super handy because perlimports could introduce changes which will later be reverted by perltidy. By running them sequentially we can avoid false positives which might be generated by perlimports changing an include which perltidy might revert. Chef's kiss, honestly. π
For an up to date example, go peep the config file which this repository uses: https://github.com/oalders/App-perlimports/blob/main/precious.toml
π Code::TidyAll π
If you're a Code::TidyAll user, you can configure perlimports as a GenericTransformer. Your config might look something like this:
[GenericTransformer perlimports]
select = **/*.{pl,pm,t,psgi}
ignore = .build/**/*
ignore = App-perlimports-*/**/*
ignore = blib/**/*
ignore = fatlib/**/*
ignore = inc/**/*
ignore = t/00-*
ignore = t/author-*
ignore = t/release-*
ignore = t/zzz-*
ignore = test-data/**/*
ignore = xt/**/*
ignore = xt/author/{pod-coverage,pod-spell,tidyall}.t
argv = --libs lib,t/lib --no-preserve-duplicates --no-preserve-unused --log-filename /tmp/perlimports.txt --log-level debug
cmd = perlimports
file_flag = -f
ok_exit_codes = 0
weight = 1
Note that in this case we've set the lowest possible weight. Why? Because we want perlimports to run before any other plugin which may transform the file. For example, you'll want perltidy to run after perlimports to avoid having to re-tidy files after your use statements have been rewritten. Order matters, bestie. π
If you want to use tidyall to run just perlimports you'll need to do something like:
tidyall --plugin "GenericTransformer perlimports" -a
For an up to date example, see the config file which this repository uses: https://github.com/oalders/App-perlimports/blob/main/tidyall.ini
π RECIPES π
Included are some examples to use if you want to employ your own file finding logic or prefer not to use config files. If this does not apply to you, you can safely skip this section. No hard feelings. π
Running perlimports on test files
find t -type f | \
grep .t$ | \
xargs perlimports \
--libs lib,t/lib \
--ignore-modules Test::More \
--no-preserve-unused \
--no-preserve-duplicates \
--log-level debug \
-i
The above command:
- finds all test files in
./t - pipes them to
perlimports - adds
libandt/libto@INC - ignores the Test::More module
- removes unused modules
- removes duplicated use statements
- displays debugging info
- edits files in place (
-i)
Users of ack can make this a touch simpler:
ack -f --perltest \
xargs perlimports \
--libs lib,t/lib \
--ignore-modules Test::More \
--no-preserve-unused \
--no-preserve-duplicates \
--log-level debug \
-i
Running perlimports on modules
find lib -type f | \
grep .pm$ | \
xargs perlimports \
--libs lib \
--no-preserve-unused \
--no-preserve-duplicates \
-i
The above command:
- finds all
.pmfiles in./lib - pipes them to
perlimports - adds
libto@INC - removes unused modules
- removes duplicated use statements
- edits files in place (
-i)
We can also make this slightly shorter by using ack:
ack -f --perl lib \
xargs perlimports \
--libs lib \
--no-preserve-unused \
--no-preserve-duplicates \
-i
π CAVEATS π
There are lots of shenanigans that Perl modules can get up to. This code will not find exports for all of those cases, but it should only attempt to rewrite imports which it knows how to handle. Please file a bug report in all other cases. Fr fr, if it breaks, tell the author β that's how it gets fixed. π«Ά
π SEE ALSO π
- Perl::Critic::Policy::TooMuchCode::ProhibitUnusedImport
- Perl::Critic::Policy::TooMuchCode::ProhibitUnusedInclude
- Perl::Critic::Policy::TooMuchCode::ProhibitUnusedConstant
π AUTHOR π
Olaf Alders <olaf@wundercounter.com>
π COPYRIGHT AND LICENSE π
This software is copyright (c) 2020 by Olaf Alders.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
aaaand that's a wrap on PERLIMPORTS(1p), bestie!! π This lil masterpiece is running on perl v5.40.1 and it's dated 2026-05-11 β so go forth, tidy those imports, and remember: unused modules are NOT the vibe. π
β¨
π¦ where this page comes from
- ubuntu stonking: perlimports 0.000060-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms