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 )