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 )