summaryrefslogtreecommitdiffstats
path: root/lib/MarkdownBook/Book.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/MarkdownBook/Book.pm')
-rw-r--r--lib/MarkdownBook/Book.pm264
1 files changed, 264 insertions, 0 deletions
diff --git a/lib/MarkdownBook/Book.pm b/lib/MarkdownBook/Book.pm
new file mode 100644
index 0000000..4f30afb
--- /dev/null
+++ b/lib/MarkdownBook/Book.pm
@@ -0,0 +1,264 @@
+# 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 <http://www.gnu.org/licenses/>.
+
+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;