-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathpr.py
More file actions
127 lines (104 loc) · 4.62 KB
/
pr.py
File metadata and controls
127 lines (104 loc) · 4.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#!/usr/bin/env python3
'''
Name: Hamdy Abou El Anein
Email: hamdy.aea@protonmail.com
Date of creation: 18-11-2024
Last update: 18-11-2024
Version: 1.0
Description: The pr command from GNU coreutils in Python3.
Example of use: python3 pr.py -n -d file.txt
'''
import sys
import os
import argparse
from datetime import datetime
class PRCommand:
def __init__(self, args):
self.opts = self.parse_arguments(args)
def parse_arguments(self, args):
"""Parse command-line arguments similar to GNU pr."""
parser = argparse.ArgumentParser(description='Paginate or columnate files for printing.')
# Page range selection
parser.add_argument('+PAGE_RANGE', nargs='?', help='First[:last] page to print')
# Columns
parser.add_argument('-c', '--columns', type=int, default=1,
help='Number of columns to print')
parser.add_argument('-a', '--across', action='store_true',
help='Print columns across rather than down')
# Spacing and formatting
parser.add_argument('-d', '--double-space', action='store_true',
help='Double space the output')
parser.add_argument('--header', default=None,
help='Header to use instead of filename')
parser.add_argument('-l', '--length', type=int, default=66,
help='Page length in lines')
parser.add_argument('-w', '--width', type=int, default=72,
help='Page width in characters')
# Line numbering
parser.add_argument('-n', '--number-lines', action='store_true',
help='Number lines')
parser.add_argument('--number-format', default='%5d',
help='Format for line numbers (default: 5 digits)')
# Other options
parser.add_argument('-t', '--omit-header', action='store_true',
help='Omit page headers and trailers')
parser.add_argument('files', nargs='*', type=str, default=['-'],
help='Files to process (- for stdin)')
return parser.parse_args(args)
def read_input(self, file_path):
"""Read input from file or stdin."""
try:
if file_path == '-':
# Check if stdin is not a tty (i.e., has piped input)
if not sys.stdin.isatty():
return sys.stdin.readlines()
return []
with open(file_path, 'r') as f:
return f.readlines()
except IOError as e:
print(f"Error reading file {file_path}: {e}", file=sys.stderr)
return []
def format_header(self, filename, page_num):
"""Create page header."""
date = datetime.now().strftime('%Y-%m-%d %H:%M')
header = self.opts.header or filename
return f"{header}\t{date}\tPage {page_num}\n"
def paginate(self):
"""Main pagination logic."""
for filename in self.opts.files:
# Read lines from file
lines = self.read_input(filename)
# Skip if no lines
if not lines:
continue
# Apply line numbering if requested
if self.opts.number_lines:
lines = [f"{self.opts.number_format % (i+1)}\t{line}"
for i, line in enumerate(lines)]
# Double spacing
if self.opts.double_space:
lines = [line.rstrip() + '\n\n' for line in lines]
# Pagination
page_num = 1
total_lines = len(lines)
line_index = 0
while line_index < total_lines:
# Print header if not omitted
if not self.opts.omit_header:
print(self.format_header(filename or 'stdin', page_num), end='')
# Print page content
page_lines = lines[line_index:line_index+self.opts.length]
print(''.join(page_lines), end='')
# Prepare for next page
line_index += self.opts.length
page_num += 1
# Add form feed between pages
if line_index < total_lines:
print('\f', end='')
# Ensure a final newline after processing each file
print()
def main():
pr = PRCommand(sys.argv[1:])
pr.paginate()
if __name__ == '__main__':
main()