Skip to content

Commit 1aea663

Browse files
committed
seq: Add seq.py and test script
1 parent 36c75e7 commit 1aea663

File tree

2 files changed

+116
-0
lines changed

2 files changed

+116
-0
lines changed

tests/seq.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env sh
2+
3+
set -eux
4+
5+
set -- \
6+
'10' \
7+
'5 10' \
8+
'0 2 10' \
9+
'1.35 0.05 2' \
10+
'-- -1.0 2' \
11+
'-- -1 2.5 3'
12+
13+
for args in "$@"; do
14+
test "$(python -m userland seq ${args})" = "$(seq ${args})"
15+
test "$(python -m userland seq -w ${args})" = "$(seq -w ${args})"
16+
done
17+
18+
exit 0

userland/utilities/seq.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import math
2+
import sys
3+
from decimal import Decimal, InvalidOperation
4+
5+
from .. import core
6+
7+
8+
parser = core.create_parser(
9+
usage=(
10+
"%prog [OPTION]... LAST",
11+
"%prog [OPTION]... FIRST LAST",
12+
"%prog [OPTION]... FIRST INCREMENT LAST",
13+
),
14+
description="Print numbers from FIRST to LAST, stepping by INCREMENT.",
15+
)
16+
17+
parser.add_option(
18+
"-f",
19+
"--format",
20+
metavar="FORMAT",
21+
help="format numbers with printf-style FORMAT",
22+
)
23+
parser.add_option(
24+
"-s",
25+
"--separator",
26+
default="\n",
27+
metavar="STRING",
28+
help="delimit outputs with STRING instead of newline",
29+
)
30+
parser.add_option(
31+
"-w",
32+
"--equal-width",
33+
action="store_true",
34+
help="pad with leading zeros to ensure equal width",
35+
)
36+
37+
38+
@core.command(parser)
39+
def python_userland_seq(opts, args):
40+
if not args:
41+
parser.error("missing operand")
42+
43+
if opts.format and opts.equal_width:
44+
parser.error("--format and --equal-width are mutually exclusive")
45+
46+
def arg_to_decimal(arg: str) -> Decimal:
47+
try:
48+
return Decimal(
49+
arg,
50+
)
51+
except InvalidOperation:
52+
print(f"invalid decimal argument: {arg}", file=sys.stderr)
53+
sys.exit(1)
54+
55+
if len(args) == 1:
56+
first = Decimal(1)
57+
increment = Decimal(1)
58+
last = arg_to_decimal(args[0])
59+
exponent = 0
60+
elif len(args) == 2:
61+
first = arg_to_decimal(args[0])
62+
exponent = first.as_tuple().exponent
63+
increment = Decimal(1)
64+
last = arg_to_decimal(args[1]).quantize(first)
65+
else:
66+
first = arg_to_decimal(args[0])
67+
increment = arg_to_decimal(args[1])
68+
exponent = min(first.as_tuple().exponent, increment.as_tuple().exponent)
69+
last = arg_to_decimal(args[2]).quantize(
70+
first
71+
if first.as_tuple().exponent < increment.as_tuple().exponent
72+
else increment
73+
)
74+
75+
formatstr: str
76+
77+
if opts.equal_width:
78+
padding = math.floor(math.log10(last))
79+
padding += -exponent + 2 if exponent else 1
80+
if first < 0 or last < 0:
81+
padding += 1
82+
83+
formatstr = f"%0{padding}.{-exponent}f" if exponent else f"%0{padding}g"
84+
elif opts.format:
85+
formatstr = opts.format
86+
else:
87+
formatstr = f"%.{-exponent}f" if exponent else "%g"
88+
89+
scale = 10**-exponent
90+
91+
print(formatstr % first, end="")
92+
for i in range(
93+
int((first + increment) * scale), int(last * scale) + 1, int(increment * scale)
94+
):
95+
print(opts.separator + formatstr % (i / scale), end="")
96+
print()
97+
98+
return 0

0 commit comments

Comments
 (0)