This commit provides an index.html file that shows the contents of the library with download links for the different formats. The index.html page and its thumbnails stored in the images/ folder can be regenerated anytime by running the build_index.py script
326 lines
10 KiB
Python
Executable file
326 lines
10 KiB
Python
Executable file
#!/usr/bin/env python
|
|
|
|
# ************************************************************************
|
|
# * Copyright (c) 2022 Yorik van Havre <yorik@uncreated.net> *
|
|
# * *
|
|
# * This program is free software; you can redistribute it and/or modify *
|
|
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
|
# * as published by the Free Software Foundation; either version 2 of *
|
|
# * the License, or (at your option) any later version. *
|
|
# * for detail see the LICENCE text file. *
|
|
# * *
|
|
# * This program is distributed in the hope that it will be useful, *
|
|
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
# * GNU Library General Public License for more details. *
|
|
# * *
|
|
# * You should have received a copy of the GNU Library General Public *
|
|
# * License along with this program; if not, write to the Free Software *
|
|
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
|
# * USA *
|
|
# * *
|
|
# ************************************************************************
|
|
|
|
"""This script produces an index.html file and an images/ folder which
|
|
shows and allows to download the contents of the library"""
|
|
|
|
import os
|
|
import zipfile
|
|
import hashlib
|
|
import urllib.request
|
|
import urllib.parse
|
|
|
|
# path definitions
|
|
homefolder = os.path.abspath(os.curdir)
|
|
imagefolder = "thumbnails"
|
|
htmlfile = os.path.join(homefolder, "index.html")
|
|
baseurl = "https://github.com/FreeCAD/FreeCAD-library/blob/master/"
|
|
excludelist = ["thumbnails"]
|
|
|
|
# icons
|
|
defaulticon = os.path.join(imagefolder, "freecad-document.svg")
|
|
gridicon = os.path.join(imagefolder, "icon-grid.svg")
|
|
listicon = os.path.join(imagefolder, "icon-list.svg")
|
|
stepicon = os.path.join(imagefolder, "icon-grey.svg")
|
|
brepicon = os.path.join(imagefolder, "icon-blue.svg")
|
|
stlicon = os.path.join(imagefolder, "icon-green.svg")
|
|
collapseicon = os.path.join(imagefolder, "icon-right.svg")
|
|
expandicon = os.path.join(imagefolder, "icon-down.svg")
|
|
|
|
# html template
|
|
template = """<html>
|
|
<head>
|
|
<title>FreeCAD Library</title>
|
|
<style>
|
|
body {
|
|
font-family: Arial,sans;
|
|
color: black;
|
|
background: white;
|
|
}
|
|
a:link, a:visited {
|
|
color: black;
|
|
text-decoration: none;
|
|
}
|
|
img {
|
|
width: 16px;
|
|
}
|
|
h1, h2, h3, h4, h5, h6, .h7, .h8, .h9 {
|
|
clear: both;
|
|
margin: 0;
|
|
cursor: pointer;
|
|
font-size: 1em;
|
|
}
|
|
.collapsable {
|
|
border-left: 1px solid grey;
|
|
padding-left: 8px;
|
|
margin-left: 5px;
|
|
}
|
|
.cards {
|
|
margin: 0;
|
|
}
|
|
.card {
|
|
float:left;
|
|
width: 128px;
|
|
margin: 4px;
|
|
}
|
|
.icon {
|
|
width: 128px;
|
|
}
|
|
.hicon {
|
|
width: 12px;
|
|
margin-right: 4px;
|
|
}
|
|
.name {
|
|
clear: left;
|
|
float: left;
|
|
overflow-wrap: break-word;
|
|
width: 128px;
|
|
}
|
|
.links {
|
|
float: left;
|
|
}
|
|
.fullwidth {
|
|
width: 100% !important;
|
|
}
|
|
.smallicon {
|
|
width: 16px !important;
|
|
margin-right: 8px;
|
|
float: left !important;
|
|
}
|
|
.largetext {
|
|
clear: none !important;
|
|
width: auto !important;
|
|
margin-right: 8px;
|
|
}
|
|
.hidden {
|
|
display: none;
|
|
}
|
|
.iconselected {
|
|
border: 2px solid black;
|
|
}
|
|
</style>
|
|
<script>
|
|
function seticon() {
|
|
collection = document.getElementsByClassName("card");
|
|
for (let i = 0; i < collection.length; i++) {
|
|
collection[i].classList.remove("fullwidth")
|
|
}
|
|
collection = document.getElementsByClassName("icon");
|
|
for (let i = 0; i < collection.length; i++) {
|
|
collection[i].classList.remove("smallicon")
|
|
}
|
|
collection = document.getElementsByClassName("name");
|
|
for (let i = 0; i < collection.length; i++) {
|
|
collection[i].classList.remove("largetext")
|
|
}
|
|
icon_icon = document.getElementById("icon_icon");
|
|
icon_icon.classList.add("iconselected")
|
|
icon_grid = document.getElementById("icon_grid");
|
|
icon_grid.classList.remove("iconselected")
|
|
}
|
|
function setgrid() {
|
|
collection = document.getElementsByClassName("card");
|
|
for (let i = 0; i < collection.length; i++) {
|
|
collection[i].classList.add("fullwidth")
|
|
}
|
|
collection = document.getElementsByClassName("icon");
|
|
for (let i = 0; i < collection.length; i++) {
|
|
collection[i].classList.add("smallicon")
|
|
}
|
|
collection = document.getElementsByClassName("name");
|
|
for (let i = 0; i < collection.length; i++) {
|
|
collection[i].classList.add("largetext")
|
|
}
|
|
icon_icon = document.getElementById("icon_icon");
|
|
icon_icon.classList.remove("iconselected")
|
|
icon_grid = document.getElementById("icon_grid");
|
|
icon_grid.classList.add("iconselected")
|
|
}
|
|
function collapse(elt) {
|
|
ndiv = elt.parentElement.nextSibling.nextSibling;
|
|
if (ndiv.classList.contains("hidden")) {
|
|
elt.src = "<!--expandicon-->";
|
|
ndiv.classList.remove("hidden");
|
|
} else {
|
|
elt.src = "<!--collapseicon-->";
|
|
ndiv.classList.add("hidden");
|
|
}
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div class="nav">
|
|
<a id="icon_icon" class="navicon iconselected" href="#" title="icon view" onclick="seticon()">
|
|
<img src="<!--gridicon-->"/>
|
|
</a>
|
|
<a id="icon_grid" class="navicon" href="#" title="list view" onclick="setgrid()">
|
|
<img src="<!--listicon-->"/>
|
|
</a>
|
|
</div>
|
|
<!--contents-->
|
|
</body>
|
|
</html>"""
|
|
|
|
|
|
def build_html(dirpath, level=1):
|
|
|
|
"""walks a directory and builds cards from its contents"""
|
|
|
|
html = ""
|
|
if os.path.isdir(dirpath):
|
|
html += build_title(dirpath, level)
|
|
if level > 1:
|
|
offset = 5 + (level - 2) * 2
|
|
html += '<div class="collapsable hidden">\n'
|
|
nodes = os.listdir(dirpath)
|
|
nodes = [node for node in nodes if node[0] != "."]
|
|
nodes = [node for node in nodes if node not in excludelist]
|
|
nodes = [os.path.join(dirpath, node) for node in nodes]
|
|
dirs = [node for node in nodes if os.path.isdir(node)]
|
|
dirs = sorted(dirs)
|
|
files = [node for node in nodes if node.lower().endswith(".fcstd")]
|
|
files = sorted(files)
|
|
for fpath in dirs:
|
|
html += build_html(fpath, level+1)
|
|
if files:
|
|
html += '<div class="cards">\n'
|
|
for fpath in files:
|
|
html += build_card(fpath)
|
|
html += '</div>\n'
|
|
if level > 1:
|
|
html += '</div>\n'
|
|
return html
|
|
|
|
|
|
def build_title(dirpath, level):
|
|
|
|
"""builds an html title from a path"""
|
|
|
|
if level == 1:
|
|
# do not print the first-level title
|
|
return ""
|
|
sl = str(level)
|
|
sn = '<img class="hicon" src="'+collapseicon+'"/>'
|
|
sn += os.path.basename(dirpath)
|
|
if level < 7:
|
|
title = '<h' + sl + ' onclick="collapse(this.children[0])">'
|
|
title += sn + '</h' + sl + '>\n'
|
|
else:
|
|
title = '<div class="h' + sl + '" onclick="collapse(this.children[0])">'
|
|
title += sn + '</div>\n'
|
|
return title
|
|
|
|
|
|
def build_card(filepath):
|
|
|
|
"""builds an HTML card for a given file"""
|
|
|
|
print("Building card for", filepath)
|
|
html = ""
|
|
if os.path.exists(filepath):
|
|
basename = os.path.splitext(filepath)[0]
|
|
name = os.path.basename(basename)
|
|
iconpath = get_icon(filepath)
|
|
raw = "?raw=true"
|
|
fileurl = baseurl + clean_path(filepath) + raw
|
|
html += '<div class="card">'
|
|
html += '<a title="FCSTD version" href="' + fileurl + '">'
|
|
html += '<img class="icon" src="' + clean_path(iconpath) + '"/>'
|
|
html += '<div class="name">' + name + '</div>'
|
|
html += '</a>'
|
|
html += '<div class="links">'
|
|
exts = {'STEP': (".stp", ".step", ".STP", ".STEP"),
|
|
'BREP': (".brp", ".brep", ".BRP", ".BREP"),
|
|
'STL': (".stl", ".STL")}
|
|
icons = {'STEP': stepicon,
|
|
'BREP': brepicon,
|
|
'STL': stlicon}
|
|
for name, exts in exts.items():
|
|
for ext in exts:
|
|
if os.path.exists(basename + ext):
|
|
exturl = baseurl + clean_path(basename + ext) + raw
|
|
html += ' <a href="' + exturl
|
|
html += '" title="' + name + ' version">'
|
|
html += '<img src="' + icons[name] + '"/>'
|
|
html += '</a>'
|
|
break
|
|
html += '</div>' # links
|
|
html += '</div>\n' # card
|
|
return html
|
|
|
|
|
|
def get_icon(filepath):
|
|
|
|
"""returns a thumbnail image path for a given file path"""
|
|
|
|
iconname = get_hashname(filepath)
|
|
iconurl = os.path.join(imagefolder, iconname)
|
|
iconpath = os.path.join(homefolder, iconurl)
|
|
try:
|
|
zfile = zipfile.ZipFile(filepath)
|
|
except Exception:
|
|
return defaulticon
|
|
if "thumbnails/Thumbnail.png" in zfile.namelist():
|
|
data = zfile.read("thumbnails/Thumbnail.png")
|
|
thumb = open(iconpath, "wb")
|
|
thumb.write(data)
|
|
thumb.close()
|
|
else:
|
|
return defaulticon
|
|
if not os.path.exists(iconpath):
|
|
return defaulticon
|
|
return iconurl
|
|
|
|
|
|
def get_hashname(filepath):
|
|
|
|
"""creates a png filename for a given file path"""
|
|
|
|
filepath = clean_path(filepath)
|
|
return hashlib.md5(filepath.encode()).hexdigest()+".png"
|
|
|
|
|
|
def clean_path(filepath):
|
|
|
|
"""cleans a file path into subfolder/subfolder/file form"""
|
|
|
|
if filepath.startswith(homefolder):
|
|
# strip local part od the path
|
|
filepath = filepath[len(homefolder):]
|
|
filepath = filepath.replace("\\", "/")
|
|
if filepath.startswith("/"):
|
|
filepath = filepath[1:]
|
|
filepath = urllib.parse.quote(filepath)
|
|
return filepath
|
|
|
|
|
|
if __name__ == "__main__":
|
|
html = build_html(homefolder)
|
|
html = template.replace("<!--contents-->", html)
|
|
html = html.replace("<!--listicon-->", listicon)
|
|
html = html.replace("<!--gridicon-->", gridicon)
|
|
html = html.replace("<!--collapseicon-->", collapseicon)
|
|
html = html.replace("<!--expandicon-->", expandicon)
|
|
with open(htmlfile, "w") as index:
|
|
index.write(html)
|
|
print("Saving", htmlfile, "... All done!")
|