—#!/usr/bin/perl
####################################################################################################
# sass (scss) compiler
####################################################################################################
use
utf8;
use
strict;
use
warnings;
####################################################################################################
# dependencies
####################################################################################################
# parse options
use
Pod::Usage;
use
Getopt::Long;
# make the options case sensitive
Getopt::Long::Configure(
"bundling"
);
# load constants from libsass
# setup encodings for std streams
# not sure why this is not default
binmode
(STDIN,
":encoding(console_in)"
);
binmode
(STDOUT,
":encoding(console_out)"
);
binmode
(STDERR,
":encoding(console_out)"
);
####################################################################################################
# normalize command arguments to utf8
####################################################################################################
# get cmd arg encoding
# convert cmd args to utf8
# now just decode every command arguments
@ARGV
=
map
{ decode(
locale
=>
$_
, 1) }
@ARGV
;
####################################################################################################
# options variables
####################################################################################################
# init options
my
$watchdog
;
my
$benchmark
;
my
$precision
;
my
$output_file
;
my
$output_style
;
my
$source_comments
;
my
$source_map_file
;
my
$source_map_root
;
my
$source_map_embed
;
my
$source_map_contents
;
my
$source_map_file_urls
;
my
$omit_source_map_url
;
# paths arrays
my
@plugin_paths
;
my
@include_paths
;
# output styles
my
$indent
=
" "
;
my
$linefeed
=
"auto"
;
####################################################################################################
####################################################################################################
# define a sub to print out the version (mimic behaviour of node.js blessc)
# this script has it's own version numbering as it's not dependent on any libs
sub
version {
printf
"psass %s (perl sass/scss compiler)\n"
,
"0.5.0"
;
printf
" libsass: %s\n"
, CSS::Sass::libsass_version();
printf
" sass2scss: %s\n"
, CSS::Sass::sass2scss_version();
exit
0 };
sub
list_plugins {
foreach
my
$plugin
(
sort
keys
%plugins
) {
printf
"--%s-plugin\n"
,
$plugin
;
}
exit
0 };
####################################################################################################
####################################################################################################
my
%options
= (
'help|h'
=>
sub
{ pod2usage(1); },
'watch|w!'
=> \
$watchdog
,
'version|v'
=> \
&version
,
'benchmark|b!'
=> \
$benchmark
,
'indent=s'
=> \
$indent
,
'linefeed=s'
=> \
$linefeed
,
'precision|p=s'
=> \
$precision
,
'output-file|o=s'
=> \
$output_file
,
'output-style|t=s'
=> \
$output_style
,
'line-numbers|c!'
=> \
$source_comments
,
'line-comments|c!'
=> \
$source_comments
,
'source-comments|c!'
=> \
$source_comments
,
'source-map-file|m=s'
=> \
$source_map_file
,
'source-map-root=s'
=> \
$source_map_root
,
'source-map-embed|e!'
=> \
$source_map_embed
,
'source-map-contents|s!'
=> \
$source_map_contents
,
'source-map-file-urls!'
=> \
$source_map_file_urls
,
'no-source-map-url!'
=> \
$omit_source_map_url
,
'plugin-path|P|L=s'
=>
sub
{
push
@plugin_paths
,
$_
[1] },
'include-path|I=s'
=>
sub
{
push
@include_paths
,
$_
[1] }
);
my
%enable_plugins
;
# add shortcuts for known plugins
foreach
my
$plugin
(
keys
%plugins
) {
$enable_plugins
{
$plugin
} = 0;
$options
{
"${plugin}-plugin!"
} =
\
$enable_plugins
{
$plugin
};
}
$options
{
'list-plugins'
} =
sub
{ list_plugins };
$options
{
'all-plugins'
} =
sub
{
foreach
my
$plugin
(
keys
%plugins
) {
$enable_plugins
{
$plugin
} = 1;
}
};
# get options
GetOptions
%options
;
# register the enabled plugin paths
foreach
my
$plugin
(
keys
%enable_plugins
) {
next
unless
$enable_plugins
{
$plugin
};
push
@plugin_paths
,
$plugins
{
$plugin
};
}
# set default if not configured
unless
(
defined
$output_style
)
{
$output_style
= SASS_STYLE_NESTED }
# parse string to constant
elsif
(
$output_style
=~ m/^n/i)
{
$output_style
= SASS_STYLE_NESTED }
elsif
(
$output_style
=~ m/^compa/i)
{
$output_style
= SASS_STYLE_COMPACT }
elsif
(
$output_style
=~ m/^compr/i)
{
$output_style
= SASS_STYLE_COMPRESSED }
elsif
(
$output_style
=~ m/^e/i)
{
$output_style
= SASS_STYLE_EXPANDED }
# die with message if style is unknown
else
{
die
"unknown output style: $output_style"
}
# resolve linefeed options
if
(
$linefeed
=~ m/^a/i)
{
$linefeed
=
undef
; }
elsif
(
$linefeed
=~ m/^w/i)
{
$linefeed
=
"\r\n"
; }
elsif
(
$linefeed
=~ m/^[u]/i)
{
$linefeed
=
"\n"
; }
elsif
(
$linefeed
=~ m/^[n]/i)
{
$linefeed
=
""
; }
# die with message if linefeed type is unknown
else
{
die
"unknown linefeed type: $linefeed"
}
# do we have output path in second arg?
if
(
defined
$ARGV
[1] &&
$ARGV
[1] ne
'-'
)
{
$output_file
=
$ARGV
[1]; }
# check if the benchmark module is available
if
(
$benchmark
&& !
eval
"use Benchmark; 1"
)
{
die
"Error loading Benchmark module\n"
, $@; }
####################################################################################################
# get sass standard option list
####################################################################################################
sub
sass_options ()
{
return
(
dont_die
=>
$watchdog
,
indent
=>
$indent
,
linefeed
=>
$linefeed
,
precision
=>
$precision
,
output_path
=>
$output_file
,
output_style
=>
$output_style
,
plugin_paths
=> \
@plugin_paths
,
include_paths
=> \
@include_paths
,
source_comments
=>
$source_comments
,
source_map_file
=>
$source_map_file
,
source_map_root
=>
$source_map_root
,
source_map_embed
=>
$source_map_embed
,
source_map_contents
=>
$source_map_contents
,
source_map_file_urls
=>
$source_map_file_urls
,
omit_source_map_url
=>
$omit_source_map_url
,
);
}
####################################################################################################
# implement our own safe File::Slurp::write_file
# since syswrite() is deprecated on :utf8 handles
####################################################################################################
# To mark FILEHANDLE as UTF-8, use :utf8 or :encoding(UTF-8) . :utf8 just marks the
# data as UTF-8 without further checking, while :encoding(UTF-8) checks the data for
# actually being valid UTF-8. More details can be found in PerlIO::encoding.
sub
write_file ($$)
{
# my ($file, $content, $binmode) = @_;
# avoid arg copies and use @_ directly
if
(
open
(
my
$fh
,
">"
,
$_
[0])) {
unless
(
flock
(
$fh
, LOCK_EX)) {
Carp::croak
"Error aquiring file lock!\n"
,
"Path: "
,
$_
[0],
"\n"
,
"Reason: "
, $!,
"\n"
;
}
unless
(
binmode
(
$fh
,
$_
[2] ||
':utf8'
)) {
Carp::croak
"Error setting file mode!\n"
,
"Path: "
,
$_
[0],
"\n"
,
"Reason: "
, $!,
"\n"
;
}
unless
(
$fh
$_
[1]) {
Carp::croak
"Error writing output!\n"
,
"Path: "
,
$_
[0],
"\n"
,
"Reason: "
, $!,
"\n"
;
}
}
else
{
Carp::croak
"Error opening writable file!\n"
,
"Path: "
,
$_
[0],
"\n"
,
"Reason: "
, $!,
"\n"
;
}
}
####################################################################################################
####################################################################################################
# first run we always want to die on error
# because we will not get any included files
our
$error
=
sub
{
die
@_
};
sub
compile ()
{
# variables
my
(
$css
,
$err
,
$stats
);
# get benchmark stamp before compiling
my
$t0
=
$benchmark
? Benchmark->new : 0;
# open filehandle if path is given
if
(
defined
$ARGV
[0] &&
$ARGV
[0] ne
'-'
)
{
(
$css
,
$err
,
$stats
) = sass_compile_file(
$ARGV
[0], sass_options()
);
}
# or use standard input
else
{
(
$css
,
$err
,
$stats
) = sass_compile(
join
(
''
, <STDIN>), sass_options()
);
}
# get benchmark stamp after compiling
my
$t1
=
$benchmark
? Benchmark->new : 0;
# only print benchmark result when module is available
if
(
$benchmark
) {
warn
timestr(timediff(
$t1
,
$t0
),
'auto'
,
'5.4f'
),
"\n"
; }
# process return status values
if
(
defined
$css
)
{
# by default we just print to standard out
unless
(
defined
$output_file
) {
$css
; }
# or if output_file is defined via options we write it there
else
{ write_file(
$output_file
,
$css
); }
}
elsif
(
defined
$err
) {
$error
->(
$err
); }
else
{
$error
->(
"fatal error - aborting"
); }
# output source-map
if
(
$source_map_file
)
{
my
$smap
=
$stats
->{
'source_map_string'
};
unless
(
$smap
) {
$error
->(
"source-map not generated <$source_map_file>"
) }
else
{ write_file(
$source_map_file
,
$smap
); }
}
# return according to expected return type
return
wantarray
? (
$css
,
$err
,
$stats
) :
$css
;
}
####################################################################################################
# main program execution
####################################################################################################
my
(
$css
,
$err
,
$stats
) = compile();
if
(
$watchdog
)
{
local
$error
=
sub
{
warn
@_
};
start_watchdog(
$stats
, \
&compile
);
}
####################################################################################################
####################################################################################################
__END__
=head1 NAME
psass - perl sass (scss) compiler
=head1 SYNOPSIS
psass [options] [ path_in | - ] [ path_out | - ]
Options:
-v, --version print version
-h, --help print this help
-w, --watch start watchdog mode
-p, --precision=int precision for float output
--indent=string set indent string used for output
--linefeed=type linefeed used for output [auto|unix|win|none]
-o, --output-file=file output file to write result to
-t, --output-style=style output style [expanded|nested|compressed|compact]
-P, --plugin-path=path plugin load path (repeatable)
-I, --include-path=path sass include path (repeatable)
-c, --source-comments enable source debug comments
-l, --line-comments synonym for --source-comments
--line-numbers synonym for --source-comments
-e, --source-map-embed embed source-map in mapping url
-s, --source-map-contents include original contents
-m, --source-map-file=file create and write source-map to file
--source-map-file-urls create file urls for source paths
--source-map-root=. specific root for relative paths
--no-source-map-url omit sourceMappingUrl from output
--benchmark print benchmark for compilation time
Plugins may be pre-installed by CSS::Sass or from 3rd parties.
There are some options available for each known plugin.
--all-plugins enables all known plugins
--list-plugin print list of all known plugins
--[name]-plugin enables the plugin with [name]
--no-[name]-plugin disabled the plugin with [name]
=head1 OPTIONS
=over 8
=item B<-help>
Print a brief help message with options and exits.
=back
=head1 DESCRIPTION
B<This program> is a sass (scss) compiler
=cut