Created
December 19, 2018 18:33
-
-
Save ghfields/53e3ecf076e1237a779a6eaee357bf0f to your computer and use it in GitHub Desktop.
ZFS Features Manual Comparison
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# A messy script that figures out features. | |
# It's... uh... messy. Sorry. | |
# Feel free to steal it, modify it to suit your own needs, et cetera. | |
# I am not responsible if this script eats your laundry. | |
# License: CC0 https://creativecommons.org/share-your-work/public-domain/cc0/ | |
# Yes, this uses manpages. | |
# | |
# Why? Because I'm not going to manually make such a table, I'm lazy. I'm not | |
# going to read through all of the ZFS implementation code to figure out if | |
# you've implemented a feature or not. I'm not going to ask people every so | |
# often about which features they've implemented. All of these options have me | |
# remembering to do work that I don't want to do. | |
# | |
# "But manpages can be wrong!" you cry out! Well, in that case you have a bug. | |
# Fix your manpages, you not-good-at-documenting-code person. | |
# | |
# "The script is wrong!" you say. Well, in that case, contact me on freenode, | |
# and tell me why it's wrong. My nick is zgrep. | |
from sys import argv | |
if len(argv) == 1: | |
path = '.' | |
elif len(argv) != 2: | |
print('Usage:', argv[0], 'path') | |
exit(1) | |
else: | |
path = argv[1] | |
from collections import defaultdict | |
from urllib.request import urlopen | |
from datetime import datetime | |
from re import sub as regex, findall | |
from json import loads as dejson | |
def zfsonlinux(): | |
sources = {'master':'https://raw.githubusercontent.com/zfsonlinux/zfs/master/man/man5/zpool-features.5'} | |
with urlopen('https://zfsonlinux.org') as web: | |
versions = findall(r'download/zfs-([0-9.]+)', web.read().decode('utf-8', 'ignore')) | |
for ver in set(versions): | |
sources[ver] = 'https://raw.githubusercontent.com/zfsonlinux/zfs/zfs-{}/man/man5/zpool-features.5'.format(ver) | |
return sources | |
def openzfsonosx(): | |
sources = {'master': 'https://raw.githubusercontent.com/openzfsonosx/zfs/master/man/man5/zpool-features.5'} | |
with urlopen('https://api.github.com/repos/openzfsonosx/zfs/tags') as web: | |
try: | |
tags = dejson(web.read().decode('utf-8', 'ignore')) | |
tags = [ x['name'].lstrip('zfs-') for x in tags ] | |
tags.sort() | |
tags = tags[-2:] | |
except: | |
tags = [] | |
for ver in tags: | |
sources[ver] = 'https://raw.githubusercontent.com/openzfsonosx/zfs/zfs-{}/man/man5/zpool-features.5'.format(ver) | |
return sources | |
def freebsd(): | |
sources = {'head': 'https://svnweb.freebsd.org/base/head/cddl/contrib/opensolaris/cmd/zpool/zpool-features.7?view=co'} | |
with urlopen('https://www.freebsd.org/releases/') as web: | |
versions = findall(r'/releases/([0-9.]+?)R', web.read().decode('utf-8', 'ignore')) | |
with urlopen('https://svnweb.freebsd.org/base/release/') as web: | |
data = web.read().decode('utf-8', 'ignore') | |
actualversions = [] | |
for ver in set(versions): | |
found = list(sorted(findall( | |
r'/base/release/(' + ver.replace('.', '\\.') + r'[0-9.]*)', | |
data | |
))) | |
if found: | |
actualversions.append(found[-1]) | |
for ver in actualversions: | |
sources[ver] = 'https://svnweb.freebsd.org/base/release/{}/cddl/contrib/opensolaris/cmd/zpool/zpool-features.7?view=co'.format(ver) | |
return sources | |
def omniosce(): | |
sources = {'master': 'https://raw.githubusercontent.com/omniosorg/illumos-omnios/master/usr/src/man/man5/zpool-features.5'} | |
with urlopen('https://omniosce.org/releasenotes.html') as web: | |
versions = findall(r'omnios-build/blob/(r[0-9]+)', web.read().decode('utf-8', 'ignore')) | |
for ver in versions: | |
sources[ver] = 'https://raw.githubusercontent.com/omniosorg/illumos-omnios/{}/usr/src/man/man5/zpool-features.5'.format(ver) | |
return sources | |
def joyent(): | |
sources = {'master': 'https://raw.githubusercontent.com/joyent/illumos-joyent/master/usr/src/man/man5/zpool-features.5'} | |
with urlopen('https://github.com/joyent/illumos-joyent') as web: | |
versions = findall(r'data-name="release-([0-9]+)"', web.read().decode('utf-8', 'ignore')) | |
versions.sort() | |
versions = versions[-2:] | |
for ver in versions: | |
sources[ver] = 'https://raw.githubusercontent.com/joyent/illumos-joyent/release-{}/usr/src/man/man5/zpool-features.5'.format(ver) | |
return sources | |
def nexenta(): | |
sources = {'master': 'https://raw.githubusercontent.com/Nexenta/illumos-nexenta/master/usr/src/man/man5/zpool-features.5'} | |
with urlopen('https://github.com/Nexenta/illumos-nexenta') as web: | |
versions = findall(r'data-name="release-([0-9.FP-]+)"', web.read().decode('utf-8', 'ignore')) | |
versions.sort() | |
for ver in versions: | |
sources[ver] = 'https://raw.githubusercontent.com/Nexenta/illumos-nexenta/release-{}/usr/src/man/man5/zpool-features.5'.format(ver) | |
return sources | |
sources = { | |
'ZFS on Linux': zfsonlinux(), | |
'FreeBSD': freebsd(), | |
'OpenZFS on OSX': openzfsonosx(), | |
'OmniOSCE': omniosce(), | |
'Joyent': joyent(), | |
'Nexenta': nexenta(), | |
'OpenZFS': { | |
'master': 'https://raw.githubusercontent.com/openzfs/openzfs/master/usr/src/man/man5/zpool-features.5', | |
}, | |
'DragonFlyBSD': { | |
'zfsport': 'https://raw.githubusercontent.com/victoredwardocallaghan/DragonFlyBSD/zfs-port/cddl/contrib/opensolaris/cmd/zpool/zpool-features.7', | |
}, | |
'NetBSD': { | |
'main': 'http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/external/cddl/osnet/sbin/zpool/zpool-features.7?only_with_tag=MAIN', | |
}, | |
# 'OpenZFS on Windows': { | |
# 'master': 'https://raw.githubusercontent.com/openzfsonwindows/ZFSin/master/ZFSin/zfs/man/man5/zpool-features.5', | |
# }, | |
} | |
features = defaultdict(list) | |
readonly = dict() | |
for name, sub in sources.items(): | |
for ver, url in sub.items(): | |
with urlopen(url) as c: | |
man = c.read().decode('utf-8') | |
for line in man.split('\n'): | |
if line.startswith('.It '): | |
line = line[4:] | |
if line.startswith('GUID'): | |
guid = line.split()[-1] | |
features[guid].append((name, ver)) | |
elif line.startswith('READ\\-ONLY COMPATIBLE'): | |
readonly[guid] = line.split()[-1] | |
header = list(sorted(sources.keys())) | |
header = list(zip(header, (sorted(sources[name], | |
key=lambda x: regex(r'[^0-9]', '', x) or x) for name in header))) | |
header.append(('Sortix', ('current',))) | |
html = open(path + '/zfs.html', 'w') | |
mw = open(path + '/zfs.txt', 'w') | |
html.write('''<!DOCTYPE html> | |
<title>ZFS Feature Matrix</title> | |
<meta charset="utf-8" /><meta name="referrer" content="never" /> | |
<style>body{font-family: "Helvetica", "Arial", sans-serif} | |
.yes{background-color:lightgreen} | |
.warn{background-color:yellow} | |
.no{background-color:lightsalmon} | |
table{border-collapse: collapse} | |
th,td{padding:0.2em 0.4em;border:1px solid #aaa;background-color:#f9f9f9} | |
th{background-color:#eaecf0} | |
th[scope=row]{text-align:left}</style> | |
''') | |
html.write('<table>\n') | |
html.write('<tr><th scope="col" rowspan="2">Feature Flag</th>') | |
html.write('<th scole="col" rowspan="2">Read-Only<br />Compatible</th>') | |
mw.write('{| class="wikitable"\n') | |
mw.write('!rowspan=2|Feature Flag\n') | |
mw.write('!rowspan=2|Read-Only<br />Compatible\n') | |
for name, vers in header: | |
html.write('<th scope="col" colspan="' + str(len(vers)) + '">' + name + '</th>') | |
mw.write('!colspan=' + str(len(vers)) + '|' + name + '\n') | |
html.write('</tr>\n<tr>') | |
mw.write('|-\n') | |
for _, vers in header: | |
for ver in vers: | |
html.write('<td>' + ver + '</td>') | |
mw.write('| ' + ver + '\n') | |
html.write('</tr>\n') | |
mw.write('|-\n') | |
for feature, names in sorted(features.items()): | |
html.write('<tr><th scope="row">' + feature + '</th>') | |
mw.write('!style="text-align:left"|' + feature + '\n') | |
if readonly[feature] == 'yes': | |
html.write('<td class="yes">yes</td>') | |
mw.write('|style="background-color:lightgreen"|yes\n') | |
else: | |
html.write('<td class="warn">no</td>') | |
mw.write('|style="background-color:yellow"|no\n') | |
for name, vers in header: | |
for ver in vers: | |
if (name, ver) in names: | |
html.write('<td class="yes">yes</td>') | |
mw.write('|style="background-color:lightgreen"|yes\n') | |
else: | |
html.write('<td class="no">no</td>') | |
mw.write('|style="background-color:lightsalmon"|no\n') | |
html.write('</tr>\n') | |
mw.write('|-\n') | |
html.write('</table>\n') | |
mw.write('|}\n') | |
now = datetime.now().isoformat() + 'Z' | |
html.write('<p>This works by parsing manpages for feature flags, and is entirely dependent on good, accurate documentation.<br />Last updated on ' + now + ' using <a href="zfs.py">zfs.py</a>.</p>\n') | |
mw.write("This works by parsing manpages for feature flags, and is entirely dependent on good, accurate documentation.<br />Last updated on " + now + " using [https://soluble.zgrep.org/zfs.py ''zfs.py''].\n") | |
html.close() | |
mw.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment