App-Music-ChordPro

 view release on metacpan or  search on metacpan

lib/ChordPro.pm  view on Meta::CPAN

one or many songs plus chord information. B<chordpro> will then
generate a photo-ready, professional looking, impress-your-friends
sheet-music suitable for printing on your nearest printer.

Typical ChordPro input:

    {title: Swing Low Sweet Chariot}
    {subtitle: Traditional}

    {start_of_chorus}
    Swing [D]low, sweet [G]chari[D]ot,
    Comin' for to carry me [A7]home.
    Swing [D7]low, sweet [G]chari[D]ot,
    Comin' for to [A7]carry me [D]home.
    {end_of_chorus}

    # Verse
    I [D]looked over Jordan, and [G]what did I [D]see,
    Comin' for to carry me [A7]home.
    A [D]band of angels [G]comin' after [D]me,
    Comin' for to [A7]carry me [D]home.

    {c: Chorus}

B<chordpro> is a rewrite of the Chordii program.

For more information about the ChordPro file format, see
L<https://www.chordpro.org>.

=cut

################ Common stuff ################

use strict;
use warnings;
use Carp;
use Text::ParseWords ();

################ The Process ################

package main;

our $options;
our $config;

package ChordPro;

use ChordPro::Paths;
use Encode qw(decode_utf8);

sub xximport {
    # Add private library.
    my $lib = CP->privlib;
    for ( @INC ) {
	return if $_ eq $lib;
    }
    unshift( @INC, $lib );
}

sub ::run {
    binmode(STDERR, ':utf8');
    binmode(STDOUT, ':utf8');
    for ( @ARGV ) {
	next if ref ne "";
	$_ = decode_utf8($_);
    }

    $options = app_setup( "ChordPro", $VERSION );
    $options->{trace}   = 1 if $options->{debug};
    $options->{verbose} = 1 if $options->{trace};
    $options->{verbose} = 9 if $options->{debug};
    main();
}

sub main {
    my ($opts) = @_;
    $options = { %$options, %$opts } if $opts;
    warn("ChordPro invoked: @{$options->{_argv}}\n") if $options->{debug};
    chordpro();

}

sub chordpro {

    # Establish backend.
    my $of = $options->{output};

    # Progress callback, if any.
    progress( callback => $options->{progress_callback}, index => -1 )
      if $options->{progress_callback};

    if ( $options->{'convert-config'} ) {
	die("\"--convert-config\" requires a single config file name\n")
	  if @ARGV;
	return ChordPro::Config::convert_config( $options->{'convert-config'}, $of );
    }

    if ( defined($of) && $of ne "" ) {
        if ( $of =~ /\.pdf$/i ) {
            $options->{generate} ||= "PDF";
        }
        elsif ( $of =~ /\.ly$/i ) {
            $options->{generate} ||= "LilyPond";
        }
        elsif ( $of =~ /\.(tex|ltx)$/i ) {
            $options->{generate} ||= "LaTeX";
        }
        elsif ( $of =~ /\.cho$/i ) {
            $options->{generate} ||= "ChordPro";
        }
        elsif ( $of =~ /\.msp$/i ) {
            $options->{generate} ||= "ChordPro";
            $options->{'backend-option'}->{variant} = "msp";
        }
        elsif ( $of =~ /\.(crd|txt)$/i ) {
            $options->{generate} ||= "Text";
        }
        elsif ( $of =~ /\.html?$/i ) {
            $options->{generate} ||= "HTML";
        }
        elsif ( $of =~ /\.mma?$/i ) {
            $options->{generate} ||= "MMA";

lib/ChordPro.pm  view on Meta::CPAN

	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>.

=item B<--csv>

When generating PDF output, also write a CSV file with titles and page
numbers. Some tools, e.g., MobileSheets, can use the CSV to process
the PDF as a collection of independent songs.

The CSV has the same name  as the PDF, with extension C<.pdf> replaced
by C<.csv>.

=item B<--decapo>

lib/ChordPro.pm  view on Meta::CPAN

    if ( $manual or $help ) {
        app_usage(\*STDOUT, 0) if $help;
        $pod2usage->(VERBOSE => 2) if $manual;
    }
    app_ident(\*STDOUT, 0) if $version;

    # If the user specified a config, it must exist.
    # Otherwise, set to a default.
    for my $config ( qw(sysconfig userconfig) ) {
        for ( $clo->{$config} ) {
            if ( defined($_) ) {
                die("$_: $!\n") unless -r $_;
                next;
            }
	    # Use default.
	    next if $clo->{nodefaultconfigs};
	    next unless $configs{$config};
            $_ = $configs{$config};
            undef($_) unless -r $_;
        }
    }
    for my $config ( qw(config) ) {
        for ( $clo->{$config} ) {
            if ( defined($_) ) {
                foreach my $c ( @$_ ) {
		    my $try = $c;
		    # Check for resource names.
		    if ( !fs_test( 'r', $try ) ) {
			$try = CP->findcfg($c);
		    }
                    die("$c: $!\n") unless $try && fs_test( 'r', $try );
		    $c = $try;
                }
                next;
            }
	    # Use default.
	    next if $clo->{nodefaultconfigs};
	    next unless $configs{$config};
            $_ = [ $configs{$config} ];
            undef($_) unless fs_test( 'fr', $_->[0] );
        }
    }
    # If no config was specified, and no default is available, force no.
    for my $config ( qw(sysconfig userconfig config ) ) {
        $clo->{"no$config"} = 1 unless $clo->{$config};
    }
    $clo->{nosongconfig} ||= $clo->{nodefaultconfigs};

    # Plug in command-line options.
    @{$options}{keys %$clo} = values %$clo;
    $::options = $options;
    # warn(::dump($options), "\n") if $options->{debug};

    # To avoid confusion, produce a template if the user asks for a
    # default config.
    # Unless she insists...
    if ( $defcfg >= 2 ) {
	use File::Copy;
	my $cfg = fn_catfile( CP->findresdirs("config")->[-1],
			      "chordpro.json" );
	binmode STDOUT => ':raw';
	copy( $cfg, \*STDOUT );
	exit 0;
    }
    if ( $tmplcfg || $defcfg ) {
	use File::Copy;
	my $cfg = fn_catfile( CP->findresdirs("config")->[-1],
			      "config.tmpl" );
	binmode STDOUT => ':raw';
	copy( $cfg, \*STDOUT );
	exit 0;
    }
    if ( $fincfg || $deltacfg ) {
	print ChordPro::Config::config_final( delta   => $deltacfg );
	exit 0;
    }

    if ( $about ) {
	$::config = ChordPro::Config::configurator({});
	app_about( \*STDOUT, $about, 0 );
    }

    if ( $dump_chords ) {
	$::config = ChordPro::Config::configurator($options);
	require ChordPro::Chords;
	ChordPro::Chords::dump_chords($dump_chords);
	exit 0;
    }

    if ( $clo->{filelist} ) {
	my @files;
	foreach ( @{ $clo->{filelist} } ) {
	    push( @files, "--filelist=" . qquote($_) );
	    my $dir = fn_dirname($_);
	    my $list = fs_load( $_, $clo );
	    push( @files, "--dir=" . qquote($dir) );
	    foreach ( @$list ) {
		next unless /\S/;
		next if /^#/;
		s/[\r\n]+$//;
		push( @files, $_ );
	    }
	    push( @files, "--filelist", "--dir" );
	}
	if ( @files ) {
	    if ( $files[0] =~ /\.pdf$/i ) {
		$options->{'front-matter'} //= $files[0];
		shift(@files);
	    }
	    if ( $files[-1] =~ /\.pdf$/i ) {
		$options->{'back-matter'} //= $files[-1];
		pop(@files);
	    }
	}
	unshift( @ARGV, @files ) if @files;
    }

    # At this point, there should be filename argument(s)
    # unless we're embedded or just dumping chords.
    app_usage(\*STDERR, 1)
      unless $::__EMBEDDED__
      || $clo->{'dump-chords'}
      || $clo->{'convert-config'}
      || @ARGV;

    # Return result.
    $options;
}



( run in 0.733 second using v1.01-cache-2.11-cpan-0bb4e1dffa6 )