Compare commits
8 Commits
cc4e26c67a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf3161ca00 | ||
|
|
8c8c08170a | ||
|
|
162dbadbe5 | ||
|
|
cf99650007 | ||
|
|
ec03da74ca | ||
|
|
5b743b5bd7 | ||
|
|
648e9f6394 | ||
|
|
c5490b1569 |
@@ -1,6 +1,6 @@
|
|||||||
click==7.1.2
|
click==7.1.2
|
||||||
fastapi==0.65.2
|
fastapi==0.65.2
|
||||||
h11==0.12.0
|
h11==0.16.0
|
||||||
pydantic==1.10.13
|
pydantic==1.10.13
|
||||||
starlette==0.40.0
|
starlette==0.40.0
|
||||||
typing-extensions==3.7.4.3
|
typing-extensions==3.7.4.3
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,52 +1,6 @@
|
|||||||
# Short links for URLs in the book
|
This file is deployed as `.htaccess` to the FPY.LI domain
|
||||||
|
to map short URLs in Fluent Python to the original URLs.
|
||||||
|
|
||||||
## Problem: link rot
|
To update it, I use tools in this other repo:
|
||||||
|
|
||||||
_Fluent Python, Second Edition_ has more than 1000 links to external resources.
|
https://github.com/pythonfluente/pythonfluente2e
|
||||||
Inevitably, some of those links will rot as time passes.
|
|
||||||
But I can't change the URLs in the print book...
|
|
||||||
|
|
||||||
## Solution: indirection
|
|
||||||
|
|
||||||
I replaced almost all URLs in the book with shortened versions that go through the `fpy.li` site which I control.
|
|
||||||
The site has an `.htaccess` file with *temporary* redirects.
|
|
||||||
|
|
||||||
When I find out a link is stale, I can thange the redirect in `.htaccess` to a new target,
|
|
||||||
which may be a link to copy in the Internet Archive's
|
|
||||||
[Wayback Machine](https://archive.org/web/)
|
|
||||||
o the link in the book is back in service through the updated redirect.
|
|
||||||
|
|
||||||
|
|
||||||
## Help wanted
|
|
||||||
|
|
||||||
Please report broken links as bugs in the [`FPY.LI.htaccess`](FPY.LI.htaccess) file.
|
|
||||||
Also, feel free to send pull requests with fixes to that file.
|
|
||||||
When I accept a PR, I will redeploy it to `fpy.li/.htaccess`.
|
|
||||||
|
|
||||||
|
|
||||||
## Details
|
|
||||||
|
|
||||||
Almost all URLs in the book are replaced with shortened versions like
|
|
||||||
[`http://fpy.li/1-3`](http://fpy.li/1-3)—for chapter 1, link #3.
|
|
||||||
|
|
||||||
There are also custom short URLs like
|
|
||||||
[`https://fpy.li/code`](https://fpy.li/code) which redirects to the example code repository.
|
|
||||||
I used custom short URLs for URLs with 3 or more mentions, or links to PEPs.
|
|
||||||
|
|
||||||
Exceptions:
|
|
||||||
|
|
||||||
- URLs with `oreilly` in them are unchanged;
|
|
||||||
- `fluentpython.com` URL (with no path) is unchanged;
|
|
||||||
|
|
||||||
The `custom.htaccess` file contains redirects with custom names
|
|
||||||
plus numbered URLs generated from the links in each chapter in
|
|
||||||
the Second Edition in English.
|
|
||||||
|
|
||||||
`short.htaccess` has redirects made by `short.py`, starting
|
|
||||||
with the Second Edition in Brazilian Portuguese.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cat custom.htaccess short.htaccess > FPY.LI.htaccess
|
|
||||||
```
|
|
||||||
|
|
||||||
`FPY.LI.htaccess` is deployed at the root folder in `http://fpy.li`.
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
scp FPY.LI.htaccess dh_i4p2ka@fpy.li:~/fpy.li/.htaccess
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# file created and managed by short.py
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import itertools
|
|
||||||
from collections.abc import Iterator
|
|
||||||
|
|
||||||
|
|
||||||
def load_redirects():
|
|
||||||
redirects = {}
|
|
||||||
targets = {}
|
|
||||||
for filename in ('custom.htaccess', 'short.htaccess'):
|
|
||||||
with open(filename) as fp:
|
|
||||||
for line in fp:
|
|
||||||
if line.startswith('RedirectTemp'):
|
|
||||||
_, short, long = line.split()
|
|
||||||
short = short[1:] # Remove leading slash
|
|
||||||
assert short not in redirects, f"{filename}: duplicate redirect from {short}"
|
|
||||||
# custom is live since 2022, we cannot change it remove duplicate targets
|
|
||||||
if not filename.startswith('custom'):
|
|
||||||
assert long not in targets, f"{filename}: Duplicate redirect to {long}"
|
|
||||||
redirects[short] = long
|
|
||||||
targets[long] = short
|
|
||||||
return redirects, targets
|
|
||||||
|
|
||||||
|
|
||||||
SDIGITS = '23456789abcdefghjkmnpqrstvwxyz'
|
|
||||||
|
|
||||||
|
|
||||||
def gen_short() -> Iterator[str]:
|
|
||||||
"""
|
|
||||||
Generate every possible sequence of SDIGITS.
|
|
||||||
"""
|
|
||||||
length = 1
|
|
||||||
while True:
|
|
||||||
for short in itertools.product(SDIGITS, repeat=length):
|
|
||||||
yield ''.join(short)
|
|
||||||
length += 1
|
|
||||||
|
|
||||||
|
|
||||||
def shorten(n: int) -> str:
|
|
||||||
"""
|
|
||||||
Get Nth short URL made from SDIGITS, where 0 is the first.
|
|
||||||
"""
|
|
||||||
iter_short = gen_short()
|
|
||||||
for _ in range(n+1):
|
|
||||||
short = next(iter_short)
|
|
||||||
return short
|
|
||||||
|
|
||||||
|
|
||||||
def gen_free_short(redirects: dict) -> Iterator[str]:
|
|
||||||
"""
|
|
||||||
Generate next available short URL.
|
|
||||||
"""
|
|
||||||
for short in gen_short():
|
|
||||||
if short not in redirects:
|
|
||||||
yield short
|
|
||||||
|
|
||||||
|
|
||||||
def new_urls(urls: list[str], redirects: dict, targets: dict) -> None:
|
|
||||||
iter_short = gen_free_short(redirects)
|
|
||||||
with open('short.htaccess', 'a') as fp:
|
|
||||||
for url in urls:
|
|
||||||
assert 'fpy.li' not in url, f"{url} is a fpy.li URL"
|
|
||||||
if url in targets:
|
|
||||||
continue
|
|
||||||
short = next(iter_short)
|
|
||||||
redirects[short] = url
|
|
||||||
targets[url] = short
|
|
||||||
fp.write(f"RedirectTemp /{short} {url}\n")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
from random import randrange
|
|
||||||
urls = [f'https://example.com/{randrange(100000)}.html' for n in range(7)]
|
|
||||||
|
|
||||||
redirects, targets = load_redirects()
|
|
||||||
new_urls(urls, redirects, targets)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
Reference in New Issue
Block a user