Litavis
view release on metacpan or search on metacpan
lib/Litavis.pm view on Meta::CPAN
$l->parse($theme_css);
my $output = $l->compile;
# File and directory input
$l->parse_file('styles.css');
$l->parse_dir('css/'); # sorted, .css only, non-recursive
# Write directly to file
$l->compile_file('output.css');
# Reset between independent compilations
$l->reset;
$l->parse($other_css);
my $fresh = $l->compile;
=head1 DESCRIPTION
Litavis is a CSS preprocessor and compiler with its entire engine implemented
in C via reusable header files, exposed to Perl through XS. It succeeds
Crayon with a focus on correctness and performance.
=head2 Features
=over 4
=item * B<Nested selectors> with flattening (C<.a { .b { } }> becomes C<.a .b { }>)
=item * B<Parent references> (C<&:hover>, C<&.active>)
=item * B<Preprocessor variables> (C<$color: red; .a { color: $color; }>)
=item * B<Mixins> (C<%box: ( padding: 8px; ); .a { %box; }>)
=item * B<Map variables> (C<%sizes: ( sm: 8px; ); .a { padding: $sizes{sm}; }>)
=item * B<Colour functions> via Colouring::In::XS (C<lighten>, C<darken>, C<mix>, C<saturate>, C<desaturate>, C<fade>, C<tint>, C<shade>, C<greyscale>)
=item * B<Cascade-aware deduplication> that only merges selectors when provably safe
=item * B<Order preservation> using C arrays (no Perl hash randomisation)
=item * B<CSS custom properties passthrough> (C<var(--x)>, C<calc()>, C<clamp()>)
=item * B<@import/@charset hoisting> to the top of output
=item * B<Hex shorthand optimisation> (C<#ffffff> becomes C<#fff>)
=item * B<Comment stripping> (block C</* */> and line C<//>)
=back
=head2 Compilation Pipeline
Each call to C<compile> processes the full accumulated AST through these
stages, all in C:
1. Flatten nested selectors
2. Resolve preprocessor variables, mixins, and map variables
3. Evaluate colour functions (lighten, darken, etc.)
4. Merge rules with the same selector (later properties win)
5. Deduplicate rules with identical properties (cascade-aware)
6. Emit CSS string (minified or pretty-printed)
C<compile> is non-destructive; calling it multiple times returns the same
result. Use C<reset> to clear state between independent compilations.
=head1 METHODS
=head2 new
my $l = Litavis->new(%options);
Create a new Litavis instance. All options are optional.
=over 4
=item B<pretty> => 0 | 1
Output mode. C<0> (default) produces minified CSS with no whitespace.
C<1> produces human-readable output with indentation and newlines.
=item B<dedupe> => 0 | 1 | 2
Deduplication strategy. C<0> disables deduplication entirely. C<1> (default)
uses conservative mode which only merges rules when no intervening rule
defines a conflicting property. C<2> uses aggressive mode which merges all
rules with identical properties regardless of cascade position.
=item B<indent> => $string
Indent string for pretty mode. Default is two spaces C<" ">. Common
alternative is C<"\t">.
=item B<shorthand_hex> => 0 | 1
Hex colour shorthand. C<1> (default) converts C<#aabbcc> to C<#abc> when
possible. C<0> preserves the original form.
=item B<sort_props> => 0 | 1
Property sorting. C<0> (default) preserves source order. C<1> alphabetises
properties within each rule.
=back
=head2 parse
$l->parse($css_string);
Parse a CSS string and accumulate the rules into the internal AST. Supports
nested selectors, preprocessor variables (C<$var: value;>), mixins
(C<%name: (...);>), map variables (C<%name: ( key: value; );>), C<@media>,
C<@keyframes>, C<@import>, and other at-rules.
Returns self for chaining.
=head2 parse_file
$l->parse_file($filename);
Read and parse a CSS file. Dies if the file cannot be opened.
Returns self for chaining.
=head2 parse_dir
$l->parse_dir($directory);
Parse all C<.css> files in a directory in alphabetical order. Non-recursive
(subdirectories are ignored). Non-CSS files are skipped.
Variables defined in earlier files (by sort order) are available to later
files, enabling patterns like C<01-vars.css>, C<02-base.css>,
C<03-theme.css>.
Returns self for chaining.
=head2 compile
my $css = $l->compile;
Compile the accumulated AST to a CSS string. Runs the full pipeline
(flatten, resolve variables, resolve colours, merge, deduplicate, emit).
Non-destructive: can be called multiple times with the same result.
=head2 compile_file
lib/Litavis.pm view on Meta::CPAN
%button-base: (
padding: 8px $pad;
border: none;
border-radius: 4px;
);
.btn-primary {
%button-base;
background: $brand;
color: white;
}
.btn-secondary {
%button-base;
background: #ecf0f1;
color: #2c3e50;
}
')->compile;
=head2 Map Variables
my $css = Litavis->new->parse('
%breakpoints: (
sm: 576px;
md: 768px;
lg: 992px;
);
.container { max-width: $breakpoints{lg}; }
')->compile;
# .container{max-width:992px;}
=head2 Colour Functions with Variables
my $css = Litavis->new->parse('
$primary: #3498db;
.btn {
background: $primary;
color: white;
}
.btn:hover {
background: darken($primary, 15%);
}
.btn:disabled {
background: desaturate($primary, 40%);
color: fade(#000, 50%);
}
')->compile;
=head2 Cascade-Aware Deduplication
# Conservative mode (default) - safe merging only
my $css = Litavis->new->parse('
.reset { color: black; margin: 0; }
.theme { color: red; }
.footer { color: black; margin: 0; }
')->compile;
# .reset and .footer are NOT merged because .theme
# defines "color" which conflicts - merging would
# reorder the cascade.
# Aggressive mode - merge all identical, ignore cascade
my $css = Litavis->new(dedupe => 2)->parse('
.a { padding: 8px; }
.b { color: red; }
.c { padding: 8px; }
')->compile;
# .a,.c{padding:8px;}.b{color:red;}
=head2 Pretty-Printed Output
my $css = Litavis->new(pretty => 1, indent => " ")->parse('
.card {
color: red;
background: blue;
}
')->compile;
# .card {
# color: red;
# background: blue;
# }
=head2 @media Queries
my $css = Litavis->new(pretty => 1)->parse('
.container { max-width: 1200px; }
@media (max-width: 768px) {
.container { max-width: 100%; padding: 0 16px; }
}
')->compile;
=head2 Multi-File Project with Directory Parsing
# css/
# 01-variables.css -> $brand: #3498db; $text: #333;
# 02-base.css -> body { color: $text; }
# 03-components.css -> .btn { background: $brand; }
my $l = Litavis->new;
$l->parse_dir('css/');
my $css = $l->compile;
# Variables from 01 are available in 02 and 03
=head2 CSS Custom Properties (Passthrough)
my $css = Litavis->new->parse('
$brand: #3498db;
:root {
--primary: $brand;
--spacing: 8px;
}
.card {
color: var(--primary);
padding: var(--spacing);
width: calc(100% - 32px);
}
')->compile;
# Preprocessor $brand is resolved; var(), calc() pass through unchanged
=head1 C HEADER FILES
The entire engine is implemented in standalone C header files that can be
reused by other XS modules:
litavis.h Master include (context struct, lifecycle)
litavis_ast.h Ordered AST with hash index
litavis_tokeniser.h Single-pass CSS tokeniser
litavis_parser.h Recursive descent parser with selector flattening
litavis_cascade.h Cascade-aware deduplication
litavis_vars.h Variable, mixin, and map resolution
litavis_colour.h Colour function evaluation (uses colouring.h)
litavis_emitter.h CSS output (minified and pretty-printed)
=head1 DEPENDENCIES
=over 4
=item * L<Colouring::In::XS> - C headers for colour manipulation
=back
=head1 SEE ALSO
L<Crayon> - the pure-Perl predecessor that Litavis replaces
=head1 AUTHOR
LNATION E<lt>email@lnation.orgE<gt>
=head1 LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
=cut
( run in 1.328 second using v1.01-cache-2.11-cpan-0fb53d1c279 )