Skip to content

Commit 4e84e07

Browse files
committed
MAINT: add website build tools
1 parent 933b1c1 commit 4e84e07

29 files changed

+1642
-0
lines changed

.gitmodules

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[submodule "website/plugins/ipynb"]
2+
path = website/plugins/ipynb
3+
url = git://github.com/danielfrg/pelican-ipynb.git
4+
[submodule "website/plugins/pelican-plugins"]
5+
path = website/plugins/pelican-plugins
6+
url = git://github.com/getpelican/pelican-plugins.git

website/.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
content/pages/*.md
2+
output
3+
content/figures
4+
content/notebooks

website/Makefile

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
PY?=python3
2+
PELICAN?=pelican
3+
PELICANOPTS=
4+
5+
BASEDIR=$(CURDIR)
6+
INPUTDIR=$(BASEDIR)/content
7+
OUTPUTDIR=$(BASEDIR)/output
8+
CONFFILE=$(BASEDIR)/pelicanconf.py
9+
PUBLISHCONF=$(BASEDIR)/publishconf.py
10+
11+
FTP_HOST=localhost
12+
FTP_USER=anonymous
13+
FTP_TARGET_DIR=/
14+
15+
SSH_HOST=localhost
16+
SSH_PORT=22
17+
SSH_USER=root
18+
SSH_TARGET_DIR=/var/www
19+
20+
S3_BUCKET=my_s3_bucket
21+
22+
CLOUDFILES_USERNAME=my_rackspace_username
23+
CLOUDFILES_API_KEY=my_rackspace_api_key
24+
CLOUDFILES_CONTAINER=my_cloudfiles_container
25+
26+
DROPBOX_DIR=~/Dropbox/Public/
27+
28+
GITHUB_PAGES_REMOTE[email protected]:jakevdp/WhirlwindTourOfPython.git
29+
GITHUB_PAGES_BRANCH=gh-pages
30+
31+
GIT_COMMIT_HASH = $(shell git rev-parse HEAD)
32+
33+
DEBUG ?= 0
34+
ifeq ($(DEBUG), 1)
35+
PELICANOPTS += -D
36+
endif
37+
38+
RELATIVE ?= 0
39+
ifeq ($(RELATIVE), 1)
40+
PELICANOPTS += --relative-urls
41+
endif
42+
43+
44+
help:
45+
@echo 'Makefile for a pelican Web site '
46+
@echo ' '
47+
@echo 'Usage: '
48+
@echo ' make html (re)generate the web site '
49+
@echo ' make clean remove the generated files '
50+
@echo ' make regenerate regenerate files upon modification '
51+
@echo ' make publish generate using production settings '
52+
@echo ' make serve [PORT=8000] serve site at http://localhost:8000'
53+
@echo ' make serve-global [SERVER=0.0.0.0] serve (as root) to $(SERVER):80 '
54+
@echo ' make devserver [PORT=8000] start/restart develop_server.sh '
55+
@echo ' make stopserver stop local server '
56+
@echo ' make ssh_upload upload the web site via SSH '
57+
@echo ' make rsync_upload upload the web site via rsync+ssh '
58+
@echo ' make dropbox_upload upload the web site via Dropbox '
59+
@echo ' make ftp_upload upload the web site via FTP '
60+
@echo ' make s3_upload upload the web site via S3 '
61+
@echo ' make cf_upload upload the web site via Cloud Files'
62+
@echo ' make github upload the web site via gh-pages '
63+
@echo ' '
64+
@echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html '
65+
@echo 'Set the RELATIVE variable to 1 to enable relative urls '
66+
@echo ' '
67+
68+
html:
69+
$(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
70+
71+
clean:
72+
[ ! -d $(OUTPUTDIR) ] || rm -rf $(OUTPUTDIR)
73+
74+
regenerate:
75+
$(PELICAN) -r $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
76+
77+
serve:
78+
ifdef PORT
79+
cd $(OUTPUTDIR) && $(PY) -m pelican.server $(PORT)
80+
else
81+
cd $(OUTPUTDIR) && $(PY) -m pelican.server
82+
endif
83+
84+
serve-global:
85+
ifdef SERVER
86+
cd $(OUTPUTDIR) && $(PY) -m pelican.server 80 $(SERVER)
87+
else
88+
cd $(OUTPUTDIR) && $(PY) -m pelican.server 80 0.0.0.0
89+
endif
90+
91+
92+
devserver:
93+
ifdef PORT
94+
$(BASEDIR)/develop_server.sh restart $(PORT)
95+
else
96+
$(BASEDIR)/develop_server.sh restart
97+
endif
98+
99+
stopserver:
100+
$(BASEDIR)/develop_server.sh stop
101+
@echo 'Stopped Pelican and SimpleHTTPServer processes running in background.'
102+
103+
publish:
104+
$(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(PUBLISHCONF) $(PELICANOPTS)
105+
106+
ssh_upload: publish
107+
scp -P $(SSH_PORT) -r $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
108+
109+
rsync_upload: publish
110+
rsync -e "ssh -p $(SSH_PORT)" -P -rvzc --delete $(OUTPUTDIR)/ $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) --cvs-exclude
111+
112+
dropbox_upload: publish
113+
cp -r $(OUTPUTDIR)/* $(DROPBOX_DIR)
114+
115+
ftp_upload: publish
116+
lftp ftp://$(FTP_USER)@$(FTP_HOST) -e "mirror -R $(OUTPUTDIR) $(FTP_TARGET_DIR) ; quit"
117+
118+
s3_upload: publish
119+
s3cmd sync $(OUTPUTDIR)/ s3://$(S3_BUCKET) --acl-public --delete-removed --guess-mime-type --no-mime-magic --no-preserve
120+
121+
cf_upload: publish
122+
cd $(OUTPUTDIR) && swift -v -A https://auth.api.rackspacecloud.com/v1.0 -U $(CLOUDFILES_USERNAME) -K $(CLOUDFILES_API_KEY) upload -c $(CLOUDFILES_CONTAINER) .
123+
124+
publish-to-github: publish
125+
ghp-import -n -m "publish-to-github from $(GIT_COMMIT_HASH)" -b blog-build $(OUTPUTDIR)
126+
git push $(GITHUB_PAGES_REMOTE) blog-build:$(GITHUB_PAGES_BRANCH)
127+
128+
publish-to-github-force: publish
129+
ghp-import -n -m "publish-to-github-force from $(GIT_COMMIT_HASH)" -b blog-build $(OUTPUTDIR)
130+
git push -f $(GITHUB_PAGES_REMOTE) blog-build:$(GITHUB_PAGES_BRANCH)
131+
132+
.PHONY: html help clean regenerate serve serve-global devserver stopserver publish ssh_upload rsync_upload dropbox_upload ftp_upload s3_upload cf_upload github

website/README.md

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Tools for creating http://jakevdp.github.io/WhirlwindTourOfPython/
2+
3+
## Building the Blog
4+
5+
Clone the repository & make sure submodules are included
6+
7+
```
8+
$ git clone https://github.com/jakevdp/WhirlwindTourOfPython.git
9+
$ git checkout origin/website
10+
$ git submodule update --init --recursive
11+
$ cd website
12+
```
13+
14+
Install the required packages:
15+
16+
```
17+
$ conda create -n pelican-blog python=3.5 jupyter notebook
18+
$ source activate pelican-blog
19+
$ pip install pelican Markdown ghp-import
20+
```
21+
22+
Build the html and serve locally:
23+
24+
```
25+
$ make html
26+
$ make serve
27+
$ open http://localhost:8000
28+
```
29+
30+
Deploy to github pages
31+
32+
```
33+
$ make publish-to-github
34+
```

website/content/favicon.ico

1.12 KB
Binary file not shown.

website/copy_notebooks.py

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""
2+
This script copies all notebooks from the book into the website directory, and
3+
creates pages which wrap them and link together.
4+
"""
5+
import os
6+
import nbformat
7+
import shutil
8+
9+
PAGEFILE = """title: {title}
10+
url:
11+
save_as: {htmlfile}
12+
Template: {template}
13+
14+
{{% notebook notebooks/{notebook_file} cells[{cells}] %}}
15+
"""
16+
17+
18+
def abspath_from_here(*args):
19+
here = os.path.dirname(__file__)
20+
path = os.path.join(here, *args)
21+
return os.path.abspath(path)
22+
23+
NB_SOURCE_DIR = abspath_from_here('..')
24+
NB_DEST_DIR = abspath_from_here('content', 'notebooks')
25+
PAGE_DEST_DIR = abspath_from_here('content', 'pages')
26+
27+
28+
def copy_notebooks():
29+
nblist = sorted(nb for nb in os.listdir(NB_SOURCE_DIR)
30+
if nb.endswith('.ipynb'))
31+
name_map = {nb: nb.rsplit('.', 1)[0].lower() + '.html'
32+
for nb in nblist}
33+
34+
figsource = abspath_from_here('..', 'fig')
35+
figdest = abspath_from_here('content', 'figures')
36+
37+
if os.path.exists(figdest):
38+
shutil.rmtree(figdest)
39+
shutil.copytree(figsource, figdest)
40+
41+
figurelist = os.listdir(figdest)
42+
figure_map = {os.path.join('fig', fig) :
43+
os.path.join('/WhirlwindTourOfPython/figures', fig)
44+
for fig in figurelist}
45+
46+
for nb in nblist:
47+
base, ext = os.path.splitext(nb)
48+
print('-', nb)
49+
50+
content = nbformat.read(os.path.join(NB_SOURCE_DIR, nb),
51+
as_version=4)
52+
53+
if nb == 'Index.ipynb':
54+
cells = '1:'
55+
template = 'page'
56+
title = 'A Whirlwind Tour of Python'
57+
else:
58+
cells = '2:'
59+
template = 'booksection'
60+
title = content.cells[2].source
61+
if not title.startswith('#') or len(title.splitlines()) > 1:
62+
raise ValueError('title not found in third cell')
63+
title = title.lstrip('#').strip()
64+
65+
# put nav below title
66+
content.cells[0], content.cells[1], content.cells[2] = content.cells[2], content.cells[0], content.cells[1]
67+
68+
# Replace internal URLs and figure links in notebook
69+
for cell in content.cells:
70+
if cell.cell_type == 'markdown':
71+
for nbname, htmlname in name_map.items():
72+
if nbname in cell.source:
73+
cell.source = cell.source.replace(nbname, htmlname)
74+
for figname, newfigname in figure_map.items():
75+
if figname in cell.source:
76+
cell.source = cell.source.replace(figname, newfigname)
77+
78+
nbformat.write(content, os.path.join(NB_DEST_DIR, nb))
79+
80+
pagefile = os.path.join(PAGE_DEST_DIR, base + '.md')
81+
htmlfile = base.lower() + '.html'
82+
with open(pagefile, 'w') as f:
83+
f.write(PAGEFILE.format(title=title,
84+
htmlfile=htmlfile,
85+
notebook_file=nb,
86+
template=template,
87+
cells=cells))
88+
89+
if __name__ == '__main__':
90+
copy_notebooks()
91+
92+

website/fabfile.py

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from fabric.api import *
2+
import fabric.contrib.project as project
3+
import os
4+
import shutil
5+
import sys
6+
import SocketServer
7+
8+
from pelican.server import ComplexHTTPRequestHandler
9+
10+
# Local path configuration (can be absolute or relative to fabfile)
11+
env.deploy_path = 'output'
12+
DEPLOY_PATH = env.deploy_path
13+
14+
# Remote server configuration
15+
production = 'root@localhost:22'
16+
dest_path = '/var/www'
17+
18+
# Rackspace Cloud Files configuration settings
19+
env.cloudfiles_username = 'my_rackspace_username'
20+
env.cloudfiles_api_key = 'my_rackspace_api_key'
21+
env.cloudfiles_container = 'my_cloudfiles_container'
22+
23+
# Github Pages configuration
24+
env.github_pages_branch = "master"
25+
26+
# Port for `serve`
27+
PORT = 8000
28+
29+
def clean():
30+
"""Remove generated files"""
31+
if os.path.isdir(DEPLOY_PATH):
32+
shutil.rmtree(DEPLOY_PATH)
33+
os.makedirs(DEPLOY_PATH)
34+
35+
def build():
36+
"""Build local version of site"""
37+
local('pelican -s pelicanconf.py')
38+
39+
def rebuild():
40+
"""`build` with the delete switch"""
41+
local('pelican -d -s pelicanconf.py')
42+
43+
def regenerate():
44+
"""Automatically regenerate site upon file modification"""
45+
local('pelican -r -s pelicanconf.py')
46+
47+
def serve():
48+
"""Serve site at http://localhost:8000/"""
49+
os.chdir(env.deploy_path)
50+
51+
class AddressReuseTCPServer(SocketServer.TCPServer):
52+
allow_reuse_address = True
53+
54+
server = AddressReuseTCPServer(('', PORT), ComplexHTTPRequestHandler)
55+
56+
sys.stderr.write('Serving on port {0} ...\n'.format(PORT))
57+
server.serve_forever()
58+
59+
def reserve():
60+
"""`build`, then `serve`"""
61+
build()
62+
serve()
63+
64+
def preview():
65+
"""Build production version of site"""
66+
local('pelican -s publishconf.py')
67+
68+
def cf_upload():
69+
"""Publish to Rackspace Cloud Files"""
70+
rebuild()
71+
with lcd(DEPLOY_PATH):
72+
local('swift -v -A https://auth.api.rackspacecloud.com/v1.0 '
73+
'-U {cloudfiles_username} '
74+
'-K {cloudfiles_api_key} '
75+
'upload -c {cloudfiles_container} .'.format(**env))
76+
77+
@hosts(production)
78+
def publish():
79+
"""Publish to production via rsync"""
80+
local('pelican -s publishconf.py')
81+
project.rsync_project(
82+
remote_dir=dest_path,
83+
exclude=".DS_Store",
84+
local_dir=DEPLOY_PATH.rstrip('/') + '/',
85+
delete=True,
86+
extra_opts='-c',
87+
)
88+
89+
def gh_pages():
90+
"""Publish to GitHub Pages"""
91+
rebuild()
92+
local("ghp-import -b {github_pages_branch} {deploy_path} -p".format(**env))

0 commit comments

Comments
 (0)