# Copyright (C) 2012 Patrick "P. J." McDermott # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . use strict; use warnings; use Carp; package MarkdownBook::Book; sub new { my ($class, $format, $dir) = @_; my $self; my $control_fh; $class = ref($class) || $class; $self = {}; bless($self, $class); unless ($format =~ m/^[a-z]+/) { Carp::croak('Invalid format "' . $format . '"'); } eval { require 'MarkdownBook/Document/' . $format . '.pm'; 1; } or Carp::croak('Unsupported format "' . $format . '"'); $self->{'format'} = $format; $self->{'dir'} = $dir; $self->{'docs'} = []; $self->{'sections'} = []; $self->{'sections_by_id'} = {}; open($control_fh, '<', $dir . '/control'); while (<$control_fh>) { chomp($_); if (m/^Title: /) { s/^Title: (.*)$/$1/; $self->{'title'} = $_; } } close($control_fh); $self->create_documents(); return $self; } sub dir { my ($self, $dir) = @_; my $old = $self->{'dir'}; $self->{'dir'} = $dir if defined($dir); return $old; } sub title { my ($self, $title) = @_; my $old = $self->{'title'}; $self->{'title'} = $title if defined($title); return $old; } sub documents { my ($self, $documents) = @_; my $old = $self->{'docs'}; $self->{'docs'} = $documents if defined($documents); return $old; } sub _get_document_module { my ($self) = @_; return 'MarkdownBook::Document::' . $self->{'format'}; } sub create_documents { my ($self) = @_; my $series_fh; my $file; my $title; my $i; my $doc; my $doc_prev; my @letters; # Create index document. $doc = $self->_get_document_module()->new($self, 'index', 'index', undef, $self->{'title'}); $doc_prev = $doc; push(@{$self->{'docs'}}, $doc); # Create chapter documents. $i = 0; open($series_fh, '<', $self->{'dir'} . '/chapters') or Carp::croak('Cannot open chapters file'); while (<$series_fh>) { chomp($_); ($file, $title) = split(/[ \t]+/, $_, 2); $doc = $self->_get_document_module()->new($self, 'chapter', $file, ++$i, $title); $doc->prev($doc_prev); $doc_prev->next($doc) if defined $doc_prev; $doc_prev = $doc; push(@{$self->{'docs'}}, $doc); } close($series_fh); # Create appendix documents. $i = -1; @letters = ('A' .. 'Z'); if (-e $self->{'dir'} . '/appendices') { open($series_fh, '<', $self->{'dir'} . '/apendices') or Carp::croak('Cannot open appendices file'); while (<$series_fh>) { chomp($_); ($file, $title) = split(/[ \t]+/, $_, 2); $doc = $self->_get_document_module()->new($self, 'appendix', $file, $letters[++$i], $title); $doc->prev($doc_prev); $doc_prev->next($doc) if defined $doc_prev; $doc_prev = $doc; push(@{$self->{'docs'}}, $doc); } close($series_fh); } } sub add_section { my ($self, $section) = @_; push(@{$self->{'sections'}}, $section); # Index sections (not documents) by ID. if (ref($section) eq 'MarkdownBook::Section') { $self->{'sections_by_id'}->{$section->id()} = $section; } } sub subst_macros { my ($self, $text) = @_; # Substitute macros with arguments. $text =~ s/ \$ # Dollar sign \[ # Left square bracket ([^\]]+) # Macro \] # Right square bracket ( \[ # Left square bracket ([^\]]+) # Macro arguments \] # Right square bracket ) /$self->_do_subst_macro($1, split(m@[ \t]+@, $3))/exg; # Substitute macros without arguments. $text =~ s/ \$ # Dollar sign \[ # Left square bracket ([^\]]+) # Macro \] # Right square bracket /$self->_do_subst_macro($1)/exg; return $text; } sub _do_subst_macro { my ($self, $macro, @args) = @_; my $sec; if ($macro eq 'toc') { return $self->_do_gen_toc(); } elsif ($macro eq 'sectlink') { if (@args != 1) { Carp::carp('Invalid arguments to "sectlink" macro'); } else { $sec = $self->{'sections_by_id'}->{$args[0]}; return '[ยง ' . $sec->number() . '][' . $sec->id() . ']'; } } else { Carp::carp("Unrecognized macro \"$macro\""); } } sub _do_gen_toc { my ($self) = @_; my $section; my $toc = ''; foreach $section (@{$self->{'sections'}}) { $toc .= "\n" if $toc ne ''; if (ref($section) =~ m/^MarkdownBook::Document/) { next if $section->type() eq 'index'; $toc .= ' * ['; $toc .= $section->id(); $toc .= ' '; $toc .= $section->title(); $toc .= ']['; $toc .= $section->file(); $toc .= ']'; } elsif (ref($section) eq 'MarkdownBook::Section') { $toc .= ' ' x $section->level(); $toc .= ($section->level() == 1 ? ' - [' : ' * ['); $toc .= $section->number(); $toc .= ' '; $toc .= $section->title(); $toc .= ']['; $toc .= $section->id(); $toc .= ']'; } } return $toc; } sub parse { my ($self) = @_; my $doc; foreach $doc (@{$self->{'docs'}}) { $self->add_section($doc); $doc->parse(); } } sub output { my ($self) = @_; my $doc; foreach $doc (@{$self->{'docs'}}) { $doc->output(); } } 1;