Group
Extension

App-termpub/lib/App/termpub.pm

package App::termpub;
use Mojo::Base 'App::termpub::Pager::HTML';
use Mojo::Util 'decode', 'getopt';
use Mojo::URL;
use Mojo::File 'tempfile',    'path';
use Mojo::JSON 'encode_json', 'decode_json';
use App::termpub::Hyphen;
use App::termpub::Epub;
use App::termpub::Pager::Text;
use Curses;

our $VERSION = '1.06';

has epub => sub {
    my $self = shift;
    App::termpub::Epub->new( filename => $self->filename );
};
has chapters => sub { shift->epub->chapters };
has chapter  => sub { shift->epub->start_chapter };
has history  => sub { [ shift->chapter ] };
has history_index => 0;

has hyphenation => sub { shift->config->{hyphenation} };
has language    => sub { shift->config->{language} };
has width       => sub { shift->config->{width} };
has 'filename';

has config => sub {
    my $self        = shift;
    my $config_file = "$ENV{HOME}/.termpubrc";
    my $config      = { hyphenation => 1, language => 'en-US', width => 80 };
    if ( !-e $config_file ) {
        my $dir = $ENV{XDG_CONFIG_HOME} // '~/.config/';
        $config_file = "$dir/termpub/config";
    }
    if ( -e $config_file ) {
        $self->read_config( path($config_file), $config );
    }
    return $config;
};

sub read_config {
    my ( $self, $file, $config ) = @_;
    my $fh = $file->open('<:encoding(UTF-8)');
    while (<$fh>) {
        s/#.*//;
        next if /^\s*$/;
        my ( $cmd, @args ) = split;
        if ( $cmd eq 'set' ) {
            my ( $key, $val ) = @args;
            if ( not exists $config->{$key} ) {
                die "Unknown variable $key in config file.\n";
            }
            $val //= 1;
            if ( $val eq 'true' || $val eq 'on' ) {
                $val = 1;
            }
            elsif ( $val eq 'false' || $val eq 'off' ) {
                $val = 0;
            }
            $config->{$key} = $val;
        }
    }
    return $config;
}

has hyphenator => sub {
    my $self = shift;
    return if !$self->hyphenation;
    my $lang = $self->epub->language || $self->language;
    my $h    = App::termpub::Hyphen->new( lang => $lang );
    return if !$h->installed;
    return $h;
};

sub parse_argv {
    my ( $self, $argv ) = @_;
    my $handler = sub { my ( $n, $v ) = @_; $self->$n($v) };
    local $SIG{__WARN__} = sub { die @_ };
    getopt( $argv, 'language|l=s' => $handler, 'hyphenation!' => $handler );
    die "Missing filename for epub.\n" if !$argv->[0];
    $self->filename( $argv->[0] );
}

sub run {
    my ( $self, $argv ) = @_;

    $self->parse_argv($argv);

    $self->pad_columns( $self->width );

    $self->title( $self->chapters->[ $self->chapter ]->title );

    my $data = $self->epub->read_metadata;

    if ( $data && $data->{position} ) {
        $self->goto_position( $data->{position} );
    }

    $self->key_bindings->{Q}                  = 'quit_without_saving';
    $self->key_bindings->{n}                  = 'next_chapter';
    $self->key_bindings->{p}                  = 'prev_chapter';
    $self->key_bindings->{h}                  = 'help_screen';
    $self->key_bindings->{o}                  = 'open_link';
    $self->key_bindings->{'?'}                = 'help_screen';
    $self->key_bindings->{t}                  = 'jump_to_toc';
    $self->key_bindings->{'<'}                = 'history_back';
    $self->key_bindings->{'>'}                = 'history_forward';
    $self->key_bindings->{'|'}                = 'set_width';
    $self->key_bindings->{Curses::KEY_RESIZE} = 'handle_resize';

    $self->SUPER::run;

}

sub quit_without_saving {
    shift->SUPER::quit;
}

sub quit {
    my $self = shift;
    $self->epub->save_metadata(
        { version => 2, position => $self->get_position } );
    $self->SUPER::quit;
}

my %keycodes = (
    Curses::KEY_DOWN      => '<Down>',
    Curses::KEY_UP        => '<Up>',
    ' '                   => '<Space>',
    Curses::KEY_NPAGE     => '<PageDown>',
    Curses::KEY_PPAGE     => '<PageUp>',
    Curses::KEY_BACKSPACE => '<Backspace>',
    Curses::KEY_HOME      => '<Home>',
    Curses::KEY_END       => '<End>',
);

sub goto_position {
    my ( $self, $position ) = @_;
    $self->set_chapter( $position->{chapter} );
    $self->SUPER::goto_position($position);
}

sub get_position {
    my $self     = shift;
    my $position = $self->SUPER::get_position;
    $position->{chapter} = $self->chapter;
    return $position;
}

sub set_width {
    my ( $self, $num ) = @_;
    $num ||= $self->prefix;
    if ($num) {
        $self->pad_columns($num);
        $self->render;
        $self->update_screen;
    }
    return;
}

sub handle_resize {
    my $self = shift;
    $self->SUPER::handle_resize;
    $self->render;
    $self->update_screen;
}

sub jump_to_toc {
    my $self = shift;
    if ( $self->epub->toc ) {
        $self->set_chapter( $self->epub->toc );
        $self->update_screen;
    }
    return;
}

sub open_image {
    my ( $self, $path ) = @_;
    my $tmp = tempfile;
    my $filename =
      Mojo::Path->new( $self->chapters->[ $self->chapter ]->filename );
    $path = $filename->merge($path)->canonicalize;
    $self->epub->archive->extractMember( $path->to_string, $tmp->to_string );
    system( 'xdg-open', $tmp );
    return;
}

sub open_link {
    my $self = shift;
    if ( $self->prefix ) {
        my ( $type, $href ) = @{ $self->hrefs->[ $self->prefix - 1 ] || [] };
        return if !$href;

        if ( $type eq 'img' ) {
            endwin;
            $self->open_image( Mojo::Path->new($href) );
            $self->update_screen;
            return;
        }

        my $url = Mojo::URL->new($href);

        if ( my $scheme = $url->scheme ) {
            endwin;
            system( 'xdg-open', $url->to_string );
            $self->update_screen;
            return;
        }

        my $path = $url->path;

        my $current_chapter = $self->chapters->[ $self->chapter ];
        $path = Mojo::Path->new( $current_chapter->filename )->merge($path);

        for ( my $i = 0 ; $i < @{ $self->chapters } ; $i++ ) {
            my $chapter = $self->chapters->[$i];
            if ( $chapter->filename eq $path ) {
                $self->set_chapter($i);

                if ( my $fragment = $url->fragment ) {
                    if ( my $line = $self->id_line->{$fragment} ) {
                        $self->line($line);
                    }
                }
                $self->update_screen;
                return;
            }
        }
    }
    return;
}

sub help_screen {
    my $self = shift;
    my @keys = sort keys %{ $self->key_bindings };

    my $length = 0;
    for my $key (@keys) {
        my $str = $keycodes{$key} || $key;
        $length = length($str) if length($str) > $length;
    }

    my $content;
    for my $key (@keys) {
        next if $key eq '' . Curses::KEY_RESIZE;
        $content .= sprintf "%-*s = %s\n", $length, $keycodes{$key} || $key,
          $self->key_bindings->{$key};
    }

    App::termpub::Pager::Text->new( content => $content )->render->run;
    $self->update_screen;
}

sub set_chapter {
    my ( $self, $num, $history ) = @_;
    return if !$self->chapters->[$num];
    if ( !$history ) {
        if ( $self->history_index != 0 ) {
            splice @{ $self->history }, 0, $self->history_index, $num;
        }
        else {
            unshift @{ $self->history }, $num;
        }
        $self->history_index(0);
    }
    $self->chapter($num);
    $self->render;
    $self->title( $self->chapters->[$num]->title );
    $self->set_mark;
    $self->line(0);
    return;
}

sub history_back {
    my $self = shift;
    return if !$self->history->[ $self->history_index + 1 ];
    $self->history_index( $self->history_index + 1 );
    $self->set_chapter( $self->history->[ $self->history_index ], 1 );
    $self->update_screen;
    return;
}

sub history_forward {
    my $self = shift;
    return if $self->history_index - 1 < 0;
    $self->history_index( $self->history_index - 1 );
    $self->set_chapter( $self->history->[ $self->history_index ], 1 );
    $self->update_screen;
    return;
}

sub next_page {
    my $self = shift;
    $self->next_chapter if !$self->SUPER::next_page;
}

sub prev_page {
    my $self = shift;
    $self->prev_chapter if !$self->SUPER::prev_page;
}

sub next_chapter {
    my $self = shift;
    while (1) {
        if ( $self->chapters->[ $self->chapter + 1 ] ) {
            $self->set_chapter( $self->chapter + 1 );
            $self->update_screen;
            return 1;
        }
        else {
            return;
        }
    }
    return;
}

sub prev_chapter {
    my $self = shift;
    while (1) {
        if ( $self->chapter > 0 ) {
            $self->set_chapter( $self->chapter - 1 );
            $self->update_screen;
            return 1;
        }
        else {
            return;
        }
    }
    return;
}

sub render {
    my $self    = shift;
    my $content = $self->chapters->[ $self->chapter ]->content;
    return $self->SUPER::render( decode( 'UTF-8', $content ) );
}

1;


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.