App-Music-ChordPro
view release on metacpan or search on metacpan
lib/ChordPro.pm view on Meta::CPAN
}
elsif ( $of =~ /\.meta$/i ) {
$options->{generate} ||= "Meta";
}
elsif ( $of =~ /\.(debug)$/i ) {
$options->{generate} ||= "Debug";
}
}
elsif ( -t STDOUT ) {
# No output, and stdout is terminal.
# Derive output name from input name.
if ( @ARGV > 1 || ( $options->{'dump-chords'} && !@ARGV ) ) {
# No default if more than one input document.
die("Please use \"--output\" to specify the output file name\n");
}
my $f = CP->sibling( $ARGV[0], ext => ".pdf" );
$options->{output} = $f;
warn("Writing output to $f\n") if $options->{verbose};
}
else {
# Write output to stdout.
$options->{output} = "-";
}
$options->{generate} ||= "PDF";
# Register backend name and load its Configurator, if any.
my $pkg = "ChordPro::Output::".$options->{generate};
$options->{backend} = $pkg;
eval "require $pkg"."::Configurator";
warn("Warning: No configurator for ", $options->{generate}, "\n$@")
if $options->{trace} && $@;
# One configurator to bind them all.
# This will also call the backend Configurator, if any.
use ChordPro::Config;
$config = ChordPro::Config::configurator({});
# Now load the real backend. Note that the actual module name
# may be changed by config.
if ( exists($config->{lc($options->{generate})})
&& exists($config->{lc($options->{generate})}->{module}) ) {
$options->{generate} = $config->{lc($options->{generate})}->{module};
}
$pkg = "ChordPro::Output::".$options->{generate};
eval "require $pkg;";
die("No backend for ", $options->{generate}, "\n$@") if $@;
$options->{backend} = $pkg;
$pkg->version if $options->{verbose} && $pkg->can("version");
# Parse the input(s).
use ChordPro::Songbook;
my $s = ChordPro::Songbook->new;
my $res;
# Shortcut a2crd conversion.
if ( $options->{a2crd} ) {
require ChordPro::A2Crd;
$res = ChordPro::A2Crd::a2crd();
push( @$res, '' );
goto WRITE_OUTPUT;
}
# Check for metadata in filelist. Actually, this works on the
# command line as well, but don't tell anybody.
progress( phase => "Parsing", index => 0,
total => 0+grep { !/^--/ } @ARGV )
if @ARGV > 1;
my %gopts;
foreach my $file ( @ARGV ) {
my @w = ( $file );
if ( $file =~ /(^|\s)--\w+/ || $file =~ /^["']/ ) {
@w = Text::ParseWords::shellwords($file);
}
my %meta;
my %defs;
my @cfg;
my %opts;
die("Error in filelist: $file\n")
unless Getopt::Long::GetOptionsFromArray
( \@w, \%opts, 'config=s@' => \@cfg, 'meta=s%' => \%meta,
'define=s%' => \%defs,
'title=s', 'subtitle=s', 'dir:s', 'filelist:s',
)
&& ( ( @w == 1 && ! keys(%opts) ) # filename
|| ( @w == 0 && keys(%opts) ) # options
);
for ( qw( title subtitle ) ) {
next unless defined $opts{$_};
$options->{$_} = $opts{$_};
}
for ( qw( filelist dir ) ) {
next unless defined $opts{$_};
$gopts{$_} = $opts{$_} eq "" ? undef : expand_tilde($opts{$_});
}
unless ( @w ) {
progress( msg => $file ) if @ARGV > 1 && $file !~ /^--/;
next;
}
$file = $w[0];
if ( defined($gopts{dir})
&& !fn_is_absolute($file) ) {
$file = fn_catfile( $gopts{dir}, $file );
}
my $opts = { meta => { map { $_, [ $meta{$_} ] } keys %meta },
defs => \%defs };
if ( @cfg ) {
$opts->{meta}->{__config} = \@cfg;
}
$opts->{generate} = $options->{generate};
# Wx runs on temp files, so pass real filename in.
$opts->{filesource} = $options->{filesource};
progress( msg => $file ) if @ARGV > 1;
$s->parse_file( $file, $opts );
}
if ( $options->{'dump-chords'} ) {
my $d = ChordPro::Song->new;
$d->{title} = "ChordPro $VERSION Built-in Chords";
$d->{subtitle} = [ "https://www.chordpro.org" ];
my @body;
my @chords;
my $prev = "";
foreach my $c ( @{ ChordPro::Chords::chordnames() } ) {
next if $c =~ /^n\.?c\.?$/i;
if ( $c =~ /^(.[b#]?)/ and $1 ne $prev ) {
$prev = $1;
push( @body, { type => "diagrams",
context => "",
origin => "__CLI__",
chords => [ @chords ]
} ) if @chords;
@chords = ();
}
push( @chords, $c );
$d->{chordsinfo}->{$c} = ChordPro::Chords::known_chord($c);
}
push( @body, { type => "diagrams",
context => "",
origin => "__CLI__",
chords => [ @chords ]
} ) if @chords;
$d->{body} = \@body;
if ( @{ $s->{songs} } == 1
&& !exists $s->{songs}->[0]->{body} ) {
$s->{songs} = [ $d ];
}
else {
push( @{ $s->{songs} }, $d );
}
}
# Try interpolations.
if ( $of ) {
my $f = fmt_subst( $s->{songs}->[0], $of );
if ( $f ne $of ) {
# Replace most non-alpha by underscore (but keep the extension).
$f =~ s;(?!\.\w+$)[^\w/-];_;g;
warn("Writing output to $f\n") if $options->{verbose};
$options->{output} = $f;
}
}
# Call backend to produce output.
$res = $pkg->generate_songbook($s);
return $res if $options->{output} eq '*';
WRITE_OUTPUT:
# Some backends write output themselves, others return an
# array of lines to be written.
if ( $res && @$res > 0 ) {
if ( $of && $of ne "-" ) {
my $fd = fs_open( $of, '>:utf8' );
push( @$res, '' ) unless $res->[-1] eq '';
print { $fd } ( join( "\n", @$res ) );
close($fd);
}
else {
binmode( STDOUT, ":utf8" );
push( @$res, '' ) unless $res->[-1] eq '';
print( join( "\n", @$res ) );
}
# Don't close STDOUT!
}
if ( $options->{verbose} ) {
my $st = json_stats;
warn("JSON: xs = ", $st->{xs}, ", rr = ", $st->{rr}, "\n");
}
}
sub ::dump {
use ChordPro::Dumper;
ddp(@_);
}
################ Options and Configuration ################
=head1 COMMAND LINE OPTIONS
=over 4
=item B<--about> (short: B<-A>)
Prints version information about the ChordPro program. No other
processing will be done.
=item B<--back-matter=>I<FILE>
Appends the contents of the named PDF document to the output. This can
be used to produce documents with back matter pages.
=item B<--config=>I<JSON> (shorter: B<--cfg>)
A JSON file that defines the behaviour of the program and the layout
of the output. See L<ChordPro::Config> for details.
This option may be specified more than once. Each additional config
file overrides the corresponding definitions that are currently
active.
=item B<--cover=>I<FILE>
Prepends the contents of the named PDF document to the output. This can
be used to add cover pages.
See also B<--title>.
( run in 0.632 second using v1.01-cache-2.11-cpan-13bb782fe5a )