1
1
#!/usr/bin/env python3
2
2
3
+ """
4
+ short.py generates unique short URLs.
5
+
6
+ This script reads lines from stdin or files named as arguments, then:
7
+
8
+ 1. retrieves or creates new short URLs, taking into account existing RedirectTemp
9
+ directives in custom.htacess or short.htacess;
10
+ 2. appends RedirectTemp directives for newly created short URLs to short.htacess;
11
+ 3. outputs the list of (short, long) URLs retrieved or created.
12
+
13
+ """
14
+
15
+ import fileinput
3
16
import itertools
4
17
from collections .abc import Iterator
18
+ from time import strftime
5
19
20
+ BASE_DOMAIN = 'fpy.li'
6
21
7
22
def load_redirects ():
8
23
redirects = {}
@@ -25,52 +40,55 @@ def load_redirects():
25
40
SDIGITS = '23456789abcdefghjkmnpqrstvwxyz'
26
41
27
42
28
- def gen_short () -> Iterator [str ]:
43
+ def gen_short (start_len = 1 ) -> Iterator [str ]:
29
44
"""
30
- Generate every possible sequence of SDIGITS.
45
+ Generate every possible sequence of SDIGITS, starting with start_len
31
46
"""
32
- length = 1
47
+ length = start_len
33
48
while True :
34
49
for short in itertools .product (SDIGITS , repeat = length ):
35
50
yield '' .join (short )
36
51
length += 1
37
52
38
53
39
- def gen_free_short (redirects : dict ) -> Iterator [str ]:
54
+ def gen_unused_short (redirects : dict ) -> Iterator [str ]:
40
55
"""
41
- Generate next available short URL.
56
+ Generate next available short URL of len >= 2 .
42
57
"""
43
- for short in gen_short ():
58
+ for short in gen_short (2 ):
44
59
if short not in redirects :
45
60
yield short
46
61
47
62
48
63
def shorten (urls : list [str ], redirects : dict , targets : dict ) -> list [tuple [str ,str ]]:
49
- """return (short, long) pairs, updating short.htaccess as needed""'
50
- iter_short = gen_free_short (redirects)
64
+ """return (short, long) pairs, appending directives to short.htaccess as needed"""
65
+ iter_short = gen_unused_short (redirects )
51
66
pairs = []
67
+ timestamp = strftime ('%Y-%m-%d %H:%M:%S' )
52
68
with open ('short.htaccess' , 'a' ) as fp :
53
69
for long in urls :
54
- assert 'fpy.li' not in long, f"{long} is a fpy.li URL"
70
+ assert BASE_DOMAIN not in long , f"{ long } is a { BASE_DOMAIN } URL"
55
71
if long in targets :
56
72
short = targets [long ]
57
73
else :
58
74
short = next (iter_short )
59
- redirects[short] = url
60
- targets[url] = short
61
- fp.write(f"RedirectTemp /{short} {url}\n ")
75
+ redirects [short ] = long
76
+ targets [long ] = short
77
+ if timestamp :
78
+ fp .write (f'\n # appended: { timestamp } \n ' )
79
+ timestamp = None
80
+ fp .write (f'RedirectTemp /{ short } { long } \n ' )
62
81
pairs .append ((short , long ))
63
82
64
83
return pairs
65
84
66
85
67
86
def main ():
68
- from random import randrange
69
- urls = [f'https://example.com/{randrange(100000)}.html' for n in range(7)]
70
-
87
+ """read URLS from filename arguments or stdin"""
88
+ urls = [line .strip () for line in fileinput .input (encoding = "utf-8" )]
71
89
redirects , targets = load_redirects ()
72
90
for short , long in shorten (urls , redirects , targets ):
73
- print(f'fpy.li /{short}\t {long }')
91
+ print (f'{ BASE_DOMAIN } /{ short } \t { long } ' )
74
92
75
93
76
94
if __name__ == '__main__' :
0 commit comments