Showing posts with label text-pager. Show all posts
Showing posts with label text-pager. Show all posts

Monday, April 10, 2017

Porting the text pager from Python to D (DLang)

By Vasudev Ram

$ tp file.txt
$ dir /s | tp


A few weeks ago, I had written this post:

tp, a simple text pager in Python

Some days ago, I thought of porting the pager from Python to D, a.k.a. the D language, and did so. The Python version was called tp.py (tp for Text Pager). So the D version is called tp.d.

The Wikipedia article about D says:

[ Though it originated as a re-engineering of C++, D is a distinct language, having redesigned some core C++ features while also taking inspiration from other languages, notably Java, Python, Ruby, C#, and Eiffel. ]

Here is the code for tp.d.
/*
File: tp.d
Purpose: A simple text pager.
Version: 0.1
Platform: Windows-only.
May be adaptable for Unix using tty / termios calls.
Only the use of getch() may need to be changed.
Author: Vasudev Ram
Copyright 2017 Vasudev Ram
Web site: https://vasudevram.github.io
Blog: https://jugad2.blogspot.com
Product store: https://gumroad.com/vasudevram
Twitter: https://mobile.twitter.com/vasudevram
*/

import std.stdio;
import std.string;
import std.file;

extern (C) int getch();

void usage(string[] args) {
    stderr.writeln("Usage: ", args[0], " text_file");
    stderr.writeln("or");
    stderr.writeln("command_generating_text | ", args[0]);
}

void pager(File in_fil, int lines_per_page=12, char quit_key='q')
{
    assert (lines_per_page > 1);
    int line_count = 0;
    int c;
    foreach (line; in_fil.byLine()) {
        writeln(line);
        line_count++;
        if ( (line_count % lines_per_page) == 0) {
            stdout.flush();
            c = getch();
            if (c == 'q')
                break;
            else
                line_count = 0;
        }
    }
}

int main(string[] args) {

    int lines_per_page = 20;
    File in_fil;

    try {
        if (args.length > 2) {
            usage(args);
            return 1;
        }
        else {
            if (args.length == 2) {
                in_fil = File(args[1]);
            }
            else {
                in_fil = stdin;
            }
            pager(in_fil, lines_per_page, 'q');
        }
    } catch (FileException fe) {
        stderr.writeln("Caught FileException: msg = ", fe.msg);
    } catch (Exception e) {
        stderr.writeln("Caught Exception: msg = ", e.msg);
    }
    finally {
        in_fil.close();
    }
    return 0;
}
Compile it the usual way with:
dmd tp.d
which will create tp.exe.
It can be run in either of two ways (paging a file or stdin), similar to the Python version. Here are two runs, dogfooding it:
tp tp.d
or
type tp.d | tp
As in the case of the Python pager (when run on itself), the output is the same as the source program itself, so I will not show it.

So, now I can say that tp is less than less :) [1] [2]

[1] Like Less is more
[2] Less in two senses of the word: it has less features, and a shorter name.

After writing this D pager, I did a brief visual comparison of it with the Python version, and was interested to see that the overall structure of the code was roughly the same, in the Python and D versions. Opening the file (or using stdin), reading lines from it, closing the file, getting characters from the keyboard, all were roughly similar in behavior. My guess that this is at least partly because both Python and D share a C heritage.

In fact, even the D line:

foreach (line; in_fil.byLine()) {

can, if you squint a bit, be read as the Python line:

for lin in fil.byLine()

which, while not valid Python, could be so, if you had a byLine method on file objects :)

For those interested, there was a good discussion about D on HN recently, here:

The reference D compiler is now open source (dlang.org)

Despite the thread title, it is more about the D language features than the licensing aspects.

Enjoy.

- Vasudev Ram - Online Python training and consulting

Get updates (via Gumroad) on my forthcoming apps and content.

Jump to posts: Python * DLang * xtopdf

Subscribe to my blog by email

My ActiveState Code recipes

Follow me on: LinkedIn * Twitter

Are you a blogger with some traffic? Get Convertkit:

Email marketing for professional bloggers



Saturday, February 11, 2017

tp, a simple text pager in Python

By Vasudev Ram

Yesterday I got this idea of writing a simple text file pager in Python.

Here it is, in file tp.py:
'''
tp.py
Purpose: A simple text pager.
Version: 0.1
Platform: Windows-only.
Can be adapted for Unix using tty / termios calls.
Only the use of msvcrt.getch() needs to be changed.
Author: Vasudev Ram
Copyright 2017 Vasudev Ram
Web site: https://vasudevram.github.io
Blog: https://jugad2.blogspot.com
Product store: https://gumroad.com/vasudevram
'''

import sys
import string
from msvcrt import getch

def pager(in_fil=sys.stdin, lines_per_page=10, quit_key='q'):
    assert lines_per_page > 1 and lines_per_page == int(lines_per_page)
    assert len(quit_key) == 1 and \
        quit_key in (string.ascii_letters + string.digits)
    lin_ctr = 0
    for lin in in_fil:
        sys.stdout.write(lin)
        lin_ctr += 1
        if lin_ctr >= lines_per_page:
            c = getch().lower()
            if c == quit_key.lower():
                break
            else:
                lin_ctr = 0

def main():
    try:
        sa, lsa = sys.argv, len(sys.argv)
        if lsa == 1:
            pager()
        elif lsa == 2:
            with open(sa[1], "r") as in_fil:
                pager(in_fil)
        else:
            sys.stderr.write
            ("Only one input file allowed in this version")
                    
    except IOError as ioe:
        sys.stderr.write("Caught IOError: {}".format(repr(ioe)))
        sys.exit(1)

    except Exception as e:
        sys.stderr.write("Caught Exception: {}".format(repr(e)))
        sys.exit(1)

if __name__ == '__main__':
    main()
I added a couple of assertions for sanity checking.

The logic of the program is fairly straightforward:

- open (for reading) the filename given as command line argument, or just read (the already-open) sys.stdin
- loop over the lines of the file, lines-per-page lines at a time
- read a character from the keyboard (without waiting for Enter, hence the use of msvcrt.getch [1])
- if it is the quit key, quit, else reset line counter and print another batch of lines
- do error handling as needed

[1] The msvcrt module is on Windows only, but there are ways to get equivalent functionality on Unixen; google for phrases like "reading a keypress on Unix without waiting for Enter", and look up Unix terms like tty, termios, curses, cbreak, etc.

And here are two runs of the program that dogfood it, one directly with a file (the program itself) as a command-line argument, and the other with the program at the end of a pipeline; output is not shown since it is the same as the input file, in both cases; you just have to press some key (other than q (which makes it quit), repeatedly, to page through the content):
$ python tp.py tp.py
$type tp.py | python tp.py

I could have golfed the code a bit, but chose not to, in the interest of the Zen of Python. Heck, Python is already Zen enough.

- Vasudev Ram - Online Python training and consulting

Get updates (via Gumroad) on my forthcoming apps and content.

Jump to posts: Python * DLang * xtopdf

Subscribe to my blog by email

My ActiveState Code recipes

Follow me on: LinkedIn * Twitter

Managed WordPress Hosting by FlyWheel