#!/usr/bin/env python
import os
import re
import sys
import unicodedata
import glob
try:
import markdown
except ImportError:
exit("This script requires the Markdown module\nInstall with: sudo pip install Markdown")
import markjaml
import pinout
import urlmapper
try:
reload(sys)
except NameError:
from importlib import reload
reload(sys)
try:
sys.setdefaultencoding('utf8')
except AttributeError: # Does not work in Python 3
unicode = str
DEBUG_LEVEL = 1
# TODO: Why is this here and not loaded from pinout.yaml
GROUND_PINS = [6, 9, 14, 20, 25, 30, 34, 39]
lang = "en"
default_strings = {
'home': 'Home',
'boards': 'Boards',
'details': 'Details',
'pin_header': '{} pin header',
'form_undefined': 'Undefined',
'group_other': 'other',
'type_hat': 'HAT form-factor',
'type_phat': 'pHAT form-factor',
'type_classic': 'Classic form-factor',
'eeprom_detect': 'Uses VID/PID',
'eeprom_setup': 'Uses EEPROM',
'uses_5v_and_3v3': 'Needs 5v and 3v3 power',
'uses_5v': 'Needs 5v power',
'uses_3v3': 'Needs 3v3 power',
'uses_i2c': 'Uses I2C',
'uses_spi': 'Uses SPI',
'uses_n_gpio_pins': 'Uses {} GPIO pins',
'bcm_pin_rev1_pi': 'GPIO/BCM pin {} on Rev 1 ( very early ) Pi',
'physical_pin_n': 'Physical/Board pin {}',
'wiring_pi_pin': 'Wiring Pi pin {}',
'made_by': 'Made by {manufacturer}',
'more_information': 'More Information',
'github_repository': 'GitHub Repository',
'board_schematic': 'Schematic',
'buy_now': 'Buy Now',
'translate_msg': 'This page needs translating, can you help?
',
'browse_addons': 'Browse more HATs, pHATs and add-ons',
'return_home': 'Return to the Raspberry Pi GPIO Pinout',
'boards_title': 'Raspberry Pi HATs, pHATs & Add-ons',
'boards_subtitle': 'Click on a HAT, pHAT or add-on for more details and to see which pins it uses!'
}
exclude_pincounts = ['3v3-power', '5v-power', 'ground', 'iface-jtag', 'i2c', 'iface-gpclk', 'wiringpi', 'spi']
def debug(level, string):
if level < DEBUG_LEVEL:
return
level_text = ['Notice', 'Warning', 'Error'][level]
print("[{}] {}".format(level_text, string))
def cssify(value):
value = slugify(value)
if value[0] == '3' or value[0] == '5':
value = 'pow' + value
return value
def slugify(value):
"""
Normalizes string, converts to lowercase, removes non-alpha characters,
and converts spaces to hyphens.
"""
value = unicode(value)
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
value = re.sub('[^\w\s-]', '', value).strip().lower()
return re.sub('[-\s]+', '_', value)
def load_overlay(overlay):
data = markjaml.load(overlay)
loaded = data['data']
loaded['source'] = overlay
loaded['long_description'] = data['html']
filename = overlay.split('/')[-1].replace('.md', '')
"""
try:
data = markjaml.load('src/{}/overlay/{}.md'.format(lang, overlay))
loaded = data['data']
loaded['source'] = "src/{}/overlay/{}.md".format(lang, overlay)
loaded['long_description'] = data['html']
except IOError:
try:
data = markjaml.load('src/{}/translate/{}.md'.format(lang, overlay))
loaded = data['data']
loaded['source'] = "src/{}/translate/{}.md".format(lang, overlay)
loaded['long_description'] = strings['translate_msg'] + data['html']
loaded['type'] = strings['group_other']
if 'formfactor' in loaded:
if str(loaded['formfactor']) == 'Custom':
loaded['formfactor'] = strings['form_undefined']
except IOError:
print('overlay {} missing in lang {}'.format(overlay, lang))
return None
"""
debug(0, '>> Rendering: {src}'.format(src=loaded['source']))
'''
If this is not an info page, then build a collection of details and append them to long_description
'''
if 'type' not in loaded or loaded['type'] != 'info':
details = []
if 'type' not in loaded:
loaded['type'] = strings['group_other']
if 'manufacturer' in loaded:
if 'collected' in loaded:
details.append(strings['made_by'].format(manufacturer=loaded['manufacturer']))
else:
manu_link = '{manufacturer}'.format(manufacturer=loaded['manufacturer'])
details.append(strings['made_by'].format(manufacturer=manu_link))
if 'pincount' in loaded:
'''
Choose correct board type to be displayed based upon pincount and form factor.
This could be a HAT, a pHAT or other...
'''
pincount = int(loaded['pincount'])
if 'formfactor' in loaded:
formfactor = str(loaded['formfactor'])
if pincount == 40 and formfactor == 'HAT':
details.append(strings['type_hat'])
elif pincount == 40 and formfactor == 'pHAT':
details.append(strings['type_phat'])
elif pincount == 40 and formfactor == '40-way':
details.append(strings['pin_header'].format(pincount))
else:
if filename not in exclude_pincounts:
details.append(strings['pin_header'].format(pincount))
elif pincount == 40:
details.append(strings['type_hat'])
elif pincount == 26:
details.append(strings['type_classic'])
else:
if filename not in exclude_pincounts:
# if '3v3-power.md' not in overlay and '5v-power.md' not in overlay and 'ground.md' not in overlay:
details.append(strings['pin_header'].format(pincount))
if 'eeprom' in loaded:
eeprom = str(loaded['eeprom'])
if eeprom == 'detect' or eeprom == 'True':
details.append(strings['eeprom_detect'])
if eeprom == 'setup':
details.append(strings['eeprom_setup'])
if 'power' not in loaded['type'] and 'power' in loaded:
uses_5v = False
uses_3v3 = False
if loaded['power'] is None:
loaded['power'] = {}
for pin in loaded['power']:
pin = str(pin)
if pin.startswith('bcm'):
pin = pinout.bcm_to_physical(pin[3:])
if pin in ['2', '4']:
uses_5v = True
if pin in ['1', '17']:
uses_3v3 = True
if uses_5v and uses_3v3:
details.append(strings['uses_5v_and_3v3'])
elif uses_5v:
details.append(strings['uses_5v'])
elif uses_3v3:
details.append(strings['uses_3v3'])
'''
If the overlay includes a collection of pins then
loop through them and count how many non-power pins are used
'''
if 'pin' in loaded:
uses_i2c = False
uses_spi = False
uses = 0
for pin in loaded['pin']:
data = loaded['pin'][pin]
pin = str(pin)
if pin.startswith('bcm'):
pin = pinout.bcm_to_physical(pin[3:])
if pin in pinout.pins:
actual_pin = pinout.pins[pin]
if actual_pin['type'] in ['+3v3', '+5v', 'GND'] and '3v3-power.md' not in overlay and '5v-power.md' not in overlay and 'ground.md' not in overlay:
raise Exception(
"{} includes a reference to a {} pin ({}), which isn't allowed".format(overlay, actual_pin['type'], pin))
else:
uses += 1
if data is not None and 'mode' in data:
if pin in ['3', '5'] and data['mode'] == 'i2c':
uses_i2c = True
if pin in ['19', '21', '23'] and data['mode'] == 'spi':
uses_spi = True
if filename not in exclude_pincounts:
if uses > 0:
details.append(strings['uses_n_gpio_pins'].format(uses))
if uses_spi:
details.append(strings['uses_spi'])
if uses_i2c:
details.append(strings['uses_i2c'])
if 'i2c' in loaded:
for addr in loaded['i2c']:
data = loaded['i2c'][addr]
addr = str(addr)
dev = data['device'].upper()
alt = None
try:
alt = data['alternate']
if type(alt) is list:
alt = ", ".join(alt)
except KeyError:
pass
if data is not None and 'device' in data:
if alt is not None:
details.append('{address}: {device} (Alt: {alt})'.format(address=addr, device=dev, alt=alt))
else:
details.append('{address}: {device}'.format(address=addr, device=dev))
# A URL to more information about the add-on board, could be a GitHub readme or an about page
if 'url' in loaded:
details.append('[{text}]({url})'.format(text=strings['more_information'], url=loaded['url']))
# Should only ever be a URL to the github repository with code supporting the product
if 'github' in loaded:
details.append('[{text}]({url})'.format(text=strings['github_repository'], url=loaded['github']))
# A URL referencing the add-on board schematic
if 'schematic' in loaded:
if loaded['schematic'] is not None:
details.append('[{text}]({url})'.format(text=strings['board_schematic'], url=loaded['schematic']))
else:
debug(1, "schematic defined in {}, but missing a value.".format(loaded['source']))
# A URL to a preferred place to buy the add-on board
if 'buy' in loaded:
details.append('[{text}]({url})'.format(text=strings['buy_now'], url=loaded['buy']))
details_html = markdown.markdown('\n'.join(map(lambda d: '* ' + d, details)))
details_image = ''
if 'image' in loaded:
details_image = "".format(loaded['image'], loaded['name'])
details_html = "
{}
{}
{}
".format(strings['details'], details_html, details_image)
if len(details) or len(details_image):
loaded['long_description'] = '{}\n{}'.format(loaded['long_description'], details_html)
else:
loaded['long_description'] = '{}'.format(loaded['long_description'])
# Automatically generate a page slug from the name if none is specified
if 'page_url' not in loaded:
loaded['page_url'] = slugify(loaded['name'])
loaded['rendered_html'] = render_overlay_page(loaded)
loaded['src'] = overlay
pages[loaded['page_url']] = loaded
navs[loaded['page_url']] = render_nav(loaded['page_url'], overlay=loaded)
return loaded
def load_md(filename):
filename = 'src/{}/{}'.format(lang, filename)
try:
html = markdown.markdown(open(filename).read(), extensions=['fenced_code'])
# print(':) Loaded markdown from {}'.format(filename))
return html
except IOError:
# print('!! Unable to load markdown from {}'.format(filename))
return ''
def render_overlay_page(overlay):
if overlay is None:
return ''
return '{}'.format(slugify(overlay['name']), overlay['long_description'])
def render_pin_page(pin_num):
pin = pinout.pins[str(pin_num)]
pin_url = pin['name']
# Exclude pages for ground pins
if pin_num in GROUND_PINS:
return None, None, None
pin_text_name = pin['name']
pin_subtext = []
pin_subtext.append(strings['physical_pin_n'].format(pin_num))
if 'scheme' in pin:
if 'bcm' in pin['scheme']:
bcm = pin['scheme']['bcm']
pin_url = 'gpio{}'.format(bcm)
pin_text_name = 'GPIO {}'.format(bcm)
pin_subtext.append('GPIO/BCM pin {}'.format(bcm))
if 'wiringpi' in pin['scheme']:
wiringpi = pin['scheme']['wiringpi']
pin_subtext.append('Wiring Pi pin {}'.format(wiringpi))
if 'bcmAlt' in pin['scheme']:
bcmAlt = pin['scheme']['bcmAlt']
pin_subtext.append(strings['bcm_pin_rev1_pi'].format(bcmAlt))
if 'description' in pin:
pin_text_name = '{} ({})'.format(pin_text_name, pin['description'])
fn_headings = []
fn_functions = []
pin_functions = ''
if 'functions' in pin:
for x in range(6):
fn_headings.append('Alt' + str(x))
function = ''
if 'alt' + str(x) in pin['functions']:
function = pin['functions']['alt' + str(x)]
fn_functions.append(function)
pin_functions = '''
'''.format(html_odd, html_even)
def get_hreflang_urls(src):
hreflang = []
for lang in sorted(alternate_urls):
if src in alternate_urls[lang]:
url = alternate_urls[lang][src]
hreflang.append(''.format(
lang=lang,
url=url
))
return hreflang
def get_lang_urls(src):
urls = []
for url_lang in sorted(alternate_urls):
if src in alternate_urls[url_lang]:
img_css = ''
if url_lang == lang:
img_css = ' class="grayscale"'
url = alternate_urls[url_lang][src]
urls.append(
''.format(
lang=url_lang,
url=url,
resource_url=resource_url,
css=img_css
))
return urls
'''
Main Program Flow
'''
if len(sys.argv) > 1:
lang = sys.argv[1]
alternate_urls = urlmapper.generate_urls(lang)
pinout.load(lang)
overlays = glob.glob("src/{}/overlay/*.md".format(lang)) + glob.glob("src/{}/translate/*.md".format(lang))
strings = pinout.get_string('strings', {})
if type(strings) == list:
_strings = {}
for item in strings:
_strings[list(item.keys())[0]] = list(item.values())[0]
strings = _strings
for key, val in default_strings.items():
if key not in strings:
strings[key] = val
base_url = pinout.get_setting('base_url', '/pinout/') # eg: '/pinout-tr/pinout/'
resource_url = pinout.get_setting('resource_url', '/resources/') # eg: '/pinout-tr/resources/'
url_suffix = pinout.get_setting('url_suffix', '') # eg: '.html'
template_main = open('common/page.html'.format(lang)).read()
template_boards = open('common/boards.html'.format(lang)).read()
template_footer = open('src/{}/template/footer.html'.format(lang)).read()
template_footer = open('src/{}/template/footer.html'.format(lang)).read()
pages = {}
navs = {}
overlays_html = []
nav_html = {}
if not os.path.isdir('output'):
try:
os.mkdir('output')
except OSError:
exit('Failed to create output dir')
if not os.path.isdir('output/{}'.format(lang)):
try:
os.mkdir('output/{}'.format(lang))
except OSError:
exit('Failed to create output/{} dir'.format(lang))
if not os.path.isdir('output/{}/pinout'.format(lang)):
try:
os.mkdir('output/{}/pinout'.format(lang))
except OSError:
exit('Failed to create output/{}/pinout dir'.format(lang))
print("\nRendering overlay pages...")
overlays = list(map(load_overlay, overlays))
overlay_subnav = ['featured']
featured_boards_count = 0
featured_boards_html = ''
boards_page = []
boards_manufacturers = []
'''
Build up the navigation between overlays. This needs to be done before rendering pages
as it's used in every single page.
overlays_html is generated with all types for legacy reasons
'''
for overlay in overlays:
link = (overlay['page_url'], overlay['name'])
overlays_html += [link]
if overlay['src'] in pinout.settings['featured'] and 'image' in overlay and featured_boards_count < 4:
featured_boards_count += 1
featured_boards_html += '
'.format(
image=image,
name=overlay['name'],
page_url=overlay['page_url'],
base_url=base_url,
type=o_type,
formfactor=o_formfactor,
manufacturer=overlay['collected'],
resource_url=resource_url)
})
def interfaces_menu(current):
interfaces = [overlay for overlay in overlays if overlay['class'] == 'interface']
html = ''
for interface in interfaces:
sel = ''
if current is not None and 'name' in current and interface['name'] == current['name']:
sel = ' class="selected"'
html += '
'.format(sel, base_url, interface['page_url'], interface['name'])
return html
boards_page = [x['html'] for x in sorted(boards_page, key=lambda k: k['name'].lower())]
pages['boards'] = {'rendered_html': ''.join(boards_page)}
'''
Manually add the index page as 'pinout', this is due to how the
website is currently structured with /pinout as the index
and all other pages in /pinout/
serve.py will mirror this structure for testing.
'''
pages['index'] = {}
pages['index']['rendered_html'] = render_overlay_page({'name': 'Index', 'long_description': load_md('/template/index.md')})
default_nav = render_nav('pinout')
navs['index'] = default_nav
'''
Add the 404 page if 404.md is present.
'''
page404 = load_md('/template/404.md')
if page404 is not None:
pages['404'] = {}
pages['404']['rendered_html'] = render_overlay_page({'name': '404', 'long_description': page404})
navs['404'] = default_nav
crumbtrail = '
'
if 'class' in pages[url] and pages[url]['class'] == 'board':
feat_boards_html = ''
body_class = 'board'
if 'collected' not in pages[url]:
crumbtrail = '