11#!/usr/bin/env python3
22
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
316import itertools
417from collections .abc import Iterator
18+ from time import strftime
519
20+ BASE_DOMAIN = 'fpy.li'
621
722def load_redirects ():
823 redirects = {}
@@ -25,52 +40,55 @@ def load_redirects():
2540SDIGITS = '23456789abcdefghjkmnpqrstvwxyz'
2641
2742
28- def gen_short () -> Iterator [str ]:
43+ def gen_short (start_len = 1 ) -> Iterator [str ]:
2944 """
30- Generate every possible sequence of SDIGITS.
45+ Generate every possible sequence of SDIGITS, starting with start_len
3146 """
32- length = 1
47+ length = start_len
3348 while True :
3449 for short in itertools .product (SDIGITS , repeat = length ):
3550 yield '' .join (short )
3651 length += 1
3752
3853
39- def gen_free_short (redirects : dict ) -> Iterator [str ]:
54+ def gen_unused_short (redirects : dict ) -> Iterator [str ]:
4055 """
41- Generate next available short URL.
56+ Generate next available short URL of len >= 2 .
4257 """
43- for short in gen_short ():
58+ for short in gen_short (2 ):
4459 if short not in redirects :
4560 yield short
4661
4762
4863def 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 )
5166 pairs = []
67+ timestamp = strftime ('%Y-%m-%d %H:%M:%S' )
5268 with open ('short.htaccess' , 'a' ) as fp :
5369 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"
5571 if long in targets :
5672 short = targets [long ]
5773 else :
5874 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 ' )
6281 pairs .append ((short , long ))
6382
6483 return pairs
6584
6685
6786def 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" )]
7189 redirects , targets = load_redirects ()
7290 for short , long in shorten (urls , redirects , targets ):
73- print(f'fpy.li /{short}\t {long }')
91+ print (f'{ BASE_DOMAIN } /{ short } \t { long } ' )
7492
7593
7694if __name__ == '__main__' :
0 commit comments