Migrated to Pelican
Now tags have pages, and all other visual goodies :)
4
.gitignore
vendored
@ -1,2 +1,6 @@
|
|||||||
_site
|
_site
|
||||||
*.swp
|
*.swp
|
||||||
|
cache
|
||||||
|
output
|
||||||
|
*.pid
|
||||||
|
*.pyc
|
||||||
|
110
Makefile
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
PY?=python
|
||||||
|
PELICAN?=pelican
|
||||||
|
PELICANOPTS=
|
||||||
|
|
||||||
|
BASEDIR=$(CURDIR)
|
||||||
|
INPUTDIR=$(BASEDIR)/content
|
||||||
|
OUTPUTDIR=$(BASEDIR)/output
|
||||||
|
CONFFILE=$(BASEDIR)/pelicanconf.py
|
||||||
|
PUBLISHCONF=$(BASEDIR)/publishconf.py
|
||||||
|
|
||||||
|
FTP_HOST=localhost
|
||||||
|
FTP_USER=anonymous
|
||||||
|
FTP_TARGET_DIR=/
|
||||||
|
|
||||||
|
SSH_HOST=localhost
|
||||||
|
SSH_PORT=22
|
||||||
|
SSH_USER=root
|
||||||
|
SSH_TARGET_DIR=/var/www
|
||||||
|
|
||||||
|
S3_BUCKET=my_s3_bucket
|
||||||
|
|
||||||
|
CLOUDFILES_USERNAME=my_rackspace_username
|
||||||
|
CLOUDFILES_API_KEY=my_rackspace_api_key
|
||||||
|
CLOUDFILES_CONTAINER=my_cloudfiles_container
|
||||||
|
|
||||||
|
DROPBOX_DIR=~/Dropbox/Public/
|
||||||
|
|
||||||
|
GITHUB_PAGES_BRANCH=master
|
||||||
|
|
||||||
|
DEBUG ?= 0
|
||||||
|
ifeq ($(DEBUG), 1)
|
||||||
|
PELICANOPTS += -D
|
||||||
|
endif
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo 'Makefile for a pelican Web site '
|
||||||
|
@echo ' '
|
||||||
|
@echo 'Usage: '
|
||||||
|
@echo ' make html (re)generate the web site '
|
||||||
|
@echo ' make clean remove the generated files '
|
||||||
|
@echo ' make regenerate regenerate files upon modification '
|
||||||
|
@echo ' make publish generate using production settings '
|
||||||
|
@echo ' make serve [PORT=8000] serve site at http://localhost:8000'
|
||||||
|
@echo ' make devserver [PORT=8000] start/restart develop_server.sh '
|
||||||
|
@echo ' make stopserver stop local server '
|
||||||
|
@echo ' make ssh_upload upload the web site via SSH '
|
||||||
|
@echo ' make rsync_upload upload the web site via rsync+ssh '
|
||||||
|
@echo ' make dropbox_upload upload the web site via Dropbox '
|
||||||
|
@echo ' make ftp_upload upload the web site via FTP '
|
||||||
|
@echo ' make s3_upload upload the web site via S3 '
|
||||||
|
@echo ' make cf_upload upload the web site via Cloud Files'
|
||||||
|
@echo ' make github upload the web site via gh-pages '
|
||||||
|
@echo ' '
|
||||||
|
@echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html'
|
||||||
|
@echo ' '
|
||||||
|
|
||||||
|
html:
|
||||||
|
$(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
[ ! -d $(OUTPUTDIR) ] || rm -rf $(OUTPUTDIR)
|
||||||
|
|
||||||
|
regenerate:
|
||||||
|
$(PELICAN) -r $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
|
||||||
|
|
||||||
|
serve:
|
||||||
|
ifdef PORT
|
||||||
|
cd $(OUTPUTDIR) && $(PY) -m pelican.server $(PORT)
|
||||||
|
else
|
||||||
|
cd $(OUTPUTDIR) && $(PY) -m pelican.server
|
||||||
|
endif
|
||||||
|
|
||||||
|
devserver:
|
||||||
|
ifdef PORT
|
||||||
|
$(BASEDIR)/develop_server.sh restart $(PORT)
|
||||||
|
else
|
||||||
|
$(BASEDIR)/develop_server.sh restart
|
||||||
|
endif
|
||||||
|
|
||||||
|
stopserver:
|
||||||
|
kill -9 `cat pelican.pid`
|
||||||
|
kill -9 `cat srv.pid`
|
||||||
|
@echo 'Stopped Pelican and SimpleHTTPServer processes running in background.'
|
||||||
|
|
||||||
|
publish:
|
||||||
|
$(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(PUBLISHCONF) $(PELICANOPTS)
|
||||||
|
|
||||||
|
ssh_upload: publish
|
||||||
|
scp -P $(SSH_PORT) -r $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
|
||||||
|
|
||||||
|
rsync_upload: publish
|
||||||
|
rsync -e "ssh -p $(SSH_PORT)" -P -rvzc --delete $(OUTPUTDIR)/ $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) --cvs-exclude
|
||||||
|
|
||||||
|
dropbox_upload: publish
|
||||||
|
cp -r $(OUTPUTDIR)/* $(DROPBOX_DIR)
|
||||||
|
|
||||||
|
ftp_upload: publish
|
||||||
|
lftp ftp://$(FTP_USER)@$(FTP_HOST) -e "mirror -R $(OUTPUTDIR) $(FTP_TARGET_DIR) ; quit"
|
||||||
|
|
||||||
|
s3_upload: publish
|
||||||
|
s3cmd sync $(OUTPUTDIR)/ s3://$(S3_BUCKET) --acl-public --delete-removed --guess-mime-type
|
||||||
|
|
||||||
|
cf_upload: publish
|
||||||
|
cd $(OUTPUTDIR) && swift -v -A https://auth.api.rackspacecloud.com/v1.0 -U $(CLOUDFILES_USERNAME) -K $(CLOUDFILES_API_KEY) upload -c $(CLOUDFILES_CONTAINER) .
|
||||||
|
|
||||||
|
github: publish
|
||||||
|
ghp-import -m "Generate Pelican site" -b $(GITHUB_PAGES_BRANCH) $(OUTPUTDIR)
|
||||||
|
git push origin $(GITHUB_PAGES_BRANCH)
|
||||||
|
|
||||||
|
.PHONY: html help clean regenerate serve devserver publish ssh_upload rsync_upload dropbox_upload ftp_upload s3_upload cf_upload github
|
@ -1,4 +0,0 @@
|
|||||||
name: Balkian's Log
|
|
||||||
pygments: true
|
|
||||||
paginate: 5
|
|
||||||
markdown: kramdown
|
|
@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
layout: default
|
|
||||||
---
|
|
||||||
<div class="postnav">
|
|
||||||
<a href="{{ page.previous.url }}"><span class="previouspost"><i class="icon-chevron-sign-left"></i> {{ page.previous.title }}</a></span>
|
|
||||||
<span class='nextpost'><a href="{{ page.next.url }}">{{ page.next.title }} <i class="icon-chevron-sign-right"></i></span></a>
|
|
||||||
</div>
|
|
||||||
<div class="posthead">
|
|
||||||
<h2 class="title">{{ page.title }}</h2>
|
|
||||||
<span class="meta date">{{ page.date | date_to_string }}</span>
|
|
||||||
{% for c in page.tags %}
|
|
||||||
<span class="label label-success">{{ c }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="post">
|
|
||||||
{{ content }}
|
|
||||||
</div>
|
|
@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
layout: post
|
|
||||||
title: "Creating my web"
|
|
||||||
date: 2013-08-22 14:14:22
|
|
||||||
tags: starters javascript ruby github git
|
|
||||||
---
|
|
||||||
|
|
||||||
Finally, I've decided to set up a decent personal page. I have settled for github-pages because I like the idea of keeping my site in a repository and having someone else host and deploy it for me. The site will be really simple, mostly static files.
|
|
||||||
Thanks to Github, [Jekyll](http://jekyllrb.com) will automatically generate static pages for my posts every time I commit anything new to this repository.
|
|
||||||
|
|
||||||
But Jekyll can be used independently, so if I ever choose to host the site myself, I can do it quite easily. Another thing that I liked about this approach is that the generated html files can be used in the future, and I will not need Jekyll to serve it.
|
|
||||||
Jekyll is really simple and most of the things are written in plain html.
|
|
||||||
That means that everything could be easily reused if I ever choose to change to another blogging framework (e.g. pelical).
|
|
||||||
But, for the time being, I like the fact that Github takes care of the compilation as well, so I can simply modify or add files through the web interface should I need to.
|
|
||||||
|
|
||||||
I hadn't played with HTML and CSS for a while now, so I also wanted to use this site as a playground.
|
|
||||||
At some point, I realised I was doing mostly everything in plain HTML and CSS, and decided to keep it like that for as long as possible. As of this writing, I haven't included any Javascript code in the page. Probably I will use some to add my [gists](http://gist.github.com/balkian) and [repositories](http://github.com/balkian), but we will see about that.
|
|
||||||
|
|
||||||
I think the code speaks for itself, so you can check out [my repository on Github](http://github.com/balkian/balkian.github.com). You can clone and deploy it easily like this:
|
|
||||||
|
|
||||||
{% highlight bash %}
|
|
||||||
git clone https://github.com/balkian/balkian.github.com
|
|
||||||
cd balkian.github.com
|
|
||||||
jekyll serve -w
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
I will keep updating this post with information about:
|
|
||||||
* Some Jekyll plugins that might be useful
|
|
||||||
* What CSS tricks I learnt
|
|
||||||
* The webfonts I used
|
|
||||||
* The badge on the left side of the page
|
|
@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
layout: post
|
|
||||||
title: "Remove git files with globbing"
|
|
||||||
date: 2013-08-22 23:14:00
|
|
||||||
tags: git
|
|
||||||
---
|
|
||||||
A simple trick. If you want to remove all the '.swp' files from a git repository, just use:
|
|
||||||
|
|
||||||
{% highlight bash %}
|
|
||||||
git rm --cached '\*\*.swp'
|
|
||||||
{% endhighlight %}
|
|
@ -1,94 +0,0 @@
|
|||||||
---
|
|
||||||
layout: post
|
|
||||||
title: "Updating EuroLoveMap"
|
|
||||||
date: 2014-03-27 14:00:00
|
|
||||||
tags: javascript python heroku
|
|
||||||
---
|
|
||||||
|
|
||||||
As part of the [OpeNER hackathon](http://www.opener-project.org/2013/07/18/opener-hackathon-in-amsterdam/) we decided to build a prototype that would allow us to compare how different countries feel about several topics.
|
|
||||||
We used the OpeNER pipeline to get the sentiment from a set of newspaper articles we gathered from media in several languages.
|
|
||||||
Then we aggregated those articles by category and country (using the source of the article or the language it was written in), obtaining the "overall feeling" of each country about each topic.
|
|
||||||
Then, we used some fancy JavaScript to make sense out of the raw information.
|
|
||||||
|
|
||||||
It didn't go too bad, it turns out [we won](http://eurosentiment.eu/wp-content/uploads/2013/07/BOLv9qnCIAAJEek.jpg).
|
|
||||||
|
|
||||||
Now, it was time for a face-lift.
|
|
||||||
I used this opportunity to play with new technologies and improve it:
|
|
||||||
|
|
||||||
* Using Flask, this time using python 3.3 and Bootstrap 3.0
|
|
||||||
* Cool HTML5+JS cards (thanks to [pastetophone](http://pastetophone.com))
|
|
||||||
* Automatic generation of fake personal data to test the interface
|
|
||||||
* Obfuscation of personal emails
|
|
||||||
|
|
||||||
Publishing a Python 3 app on Heroku
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
[seen here](http://eurolovemap.herokuapp.com/)
|
|
||||||
|
|
||||||
{% highlight bash %}
|
|
||||||
mkvirtualenv -p /usr/bin/python3.3 eurolovemap
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
Since Heroku uses python 2.7 by default, we have to tell it which version we want, although it supports python 3.4 as well.
|
|
||||||
I couldn't get python 3.4 working using the [deadsnakes](https://launchpad.net/~fkrull/+archive/deadsnakes) ppa, so I used python 3.3 instead, which works fine but is not officially supported.
|
|
||||||
Just create a file named *runtime.txt* in your project root, with the python version you want to use:
|
|
||||||
|
|
||||||
{% highlight bash %}
|
|
||||||
python-3.3.1
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
Don't forget to freeze your dependencies so Heroku can install them:
|
|
||||||
{% highlight bash %}
|
|
||||||
pip freze > requirements.txt
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
Publishing personal emails
|
|
||||||
--------------------------
|
|
||||||
There are really sophisticated and effective ways to obfuscate personal emails so that spammers cannot easily grab yours.
|
|
||||||
However, this time I needed something really simple to hide our emails from the simplest form of crawlers.
|
|
||||||
Most of the team are in academia somehow, so in the end all our emails are available in sites like Google Scholar.
|
|
||||||
Anyway, nobody likes getting spammed so I settled for a custom [Caesar cipher](http://en.wikipedia.org/wiki/Caesar_cipher).
|
|
||||||
Please, don't use it for any serious application if you are concerned about being spammed.
|
|
||||||
|
|
||||||
{% highlight python %}
|
|
||||||
def blur_email(email):
|
|
||||||
return "".join([chr(ord(i)+5) for i in email])
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
And this is the client side:
|
|
||||||
|
|
||||||
{% highlight javascript %}
|
|
||||||
window.onload = function(){
|
|
||||||
elems = document.getElementsByClassName('profile-email');
|
|
||||||
for(var e in elems){
|
|
||||||
var blur = elems[e].innerHTML;
|
|
||||||
var email = "";
|
|
||||||
for(var s in blur){
|
|
||||||
var a = blur.charCodeAt(s)
|
|
||||||
email = email+String.fromCharCode(a-5);
|
|
||||||
}
|
|
||||||
elems[e].innerHTML = email;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
Unfortunately, this approach does not hide your email from anyone using [PhantomJS](http://phantomjs.org/), [ZombieJS](http://zombie.labnotes.org/) or similar.
|
|
||||||
For that, other approaches like generating a picture with the address would be necessary.
|
|
||||||
Nevertheless, it is overkill for a really simple ad-hoc application with custom formatting and just a bunch of emails that would easily be grabbed manually.
|
|
||||||
|
|
||||||
Generation of fake data
|
|
||||||
-----------------------
|
|
||||||
To test the contact section of the site, I wanted to populate it with fake data.
|
|
||||||
[Fake-Factory](https://github.com/joke2k/faker) is an amazing library that can generate fake data of almost any kind: emails, association names, acronyms...
|
|
||||||
It even lets you localise the results (get Spanish names, for instance) and generate factories for certain classes (à la Django).
|
|
||||||
|
|
||||||
But I also wanted pictures, enter [Lorem Pixel](http://lorempixel.com/).
|
|
||||||
With its API you can generate pictures of almost any size, for different topics (e.g. nightlife, people) and with a custom text.
|
|
||||||
You can even use an index, so it will always show the same picture.
|
|
||||||
|
|
||||||
For instance, the picture below is served through Lorem Pixel.
|
|
||||||
|
|
||||||
![This picture is generated with LoremIpsum](http://lorempixel.com/400/200/nightlife/)
|
|
||||||
|
|
||||||
By the way, if you only want cat pictures, take a look at [Placekitten](http://placekitten.com/).
|
|
||||||
And for NSFW text, there's the [Samuel L. Jackson Ipsum](http://slipsum.com/)
|
|
@ -1,106 +0,0 @@
|
|||||||
---
|
|
||||||
layout: post
|
|
||||||
title: "Publishing in PyPi"
|
|
||||||
date: 2014-09-27 10:00:00
|
|
||||||
tags: github python pypi
|
|
||||||
---
|
|
||||||
|
|
||||||
Developing a python module and publishing it on Github is cool, but most of the times you want others to download and use it easily.
|
|
||||||
That is the role of PyPi, the python package repository.
|
|
||||||
In this post I show you how to publish your package in less than 10 minutes.
|
|
||||||
|
|
||||||
## Choose a fancy name
|
|
||||||
|
|
||||||
If you haven't done so yet, take a minute or two to think about this.
|
|
||||||
To publish on PyPi you need a name for your package that isn't taken.
|
|
||||||
What's more, a catchy and unique name will help people remember your module and feel more inclined to at least try it.
|
|
||||||
|
|
||||||
The package name should hint what your module does, but that's not always the case.
|
|
||||||
That's your call.
|
|
||||||
I personally put uniqueness and memorability over describing the functionality.
|
|
||||||
|
|
||||||
## Create a .pypirc configuration file
|
|
||||||
{% highlight cfg %}
|
|
||||||
[distutils] # this tells distutils what package indexes you can push to
|
|
||||||
index-servers =
|
|
||||||
pypi # the live PyPI
|
|
||||||
pypitest # test PyPI
|
|
||||||
|
|
||||||
[pypi] # authentication details for live PyPI
|
|
||||||
repository = https://pypi.python.org/pypi
|
|
||||||
username = { your_username }
|
|
||||||
password = { your_password } # not necessary
|
|
||||||
|
|
||||||
[pypitest] # authentication details for test PyPI
|
|
||||||
repository = https://testpypi.python.org/pypi
|
|
||||||
username = { your_username }
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
As you can see, you need to register both in the [main pypi repository](https://pypi.python.org/pypi?%3Aaction=register_form) and the [testing server](https://testpypi.python.org/pypi?%3Aaction=register_form).
|
|
||||||
The usernames and passwords might be different, that is up to you!
|
|
||||||
|
|
||||||
## Prepare your package
|
|
||||||
|
|
||||||
{% highlight raw %}
|
|
||||||
root-dir/ # Any name you want
|
|
||||||
setup.py
|
|
||||||
setup.cfg
|
|
||||||
LICENSE.txt
|
|
||||||
README.md
|
|
||||||
mypackage/
|
|
||||||
__init__.py
|
|
||||||
foo.py
|
|
||||||
bar.py
|
|
||||||
baz.py
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
### setup.cfg
|
|
||||||
|
|
||||||
{% highlight cfg %}
|
|
||||||
[metadata]
|
|
||||||
description-file = README.md
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
The markdown README is the _de facto_ standard in Github, but you can also use rST (reStructuredText), the standard in the python community.
|
|
||||||
|
|
||||||
### setup.py
|
|
||||||
|
|
||||||
{% highlight python %}
|
|
||||||
from distutils.core import setup
|
|
||||||
setup(
|
|
||||||
name = 'mypackage',
|
|
||||||
packages = ['mypackage'], # this must be the same as the name above
|
|
||||||
version = '{ version }',
|
|
||||||
description = '{ description }',
|
|
||||||
author = '{ name }',
|
|
||||||
author_email = '{ email }',
|
|
||||||
url = 'https://github.com/{user}/{package}', # URL to the github repo
|
|
||||||
download_url = 'https://github.com/{user}/{repo}/tarball/{version}',
|
|
||||||
keywords = ['websockets', 'display', 'd3'], # list of keywords that represent your package
|
|
||||||
classifiers = [],
|
|
||||||
)
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
You might notice that the download_url points to a Github URL.
|
|
||||||
We could host our package anywhere, but Github is a convenient option.
|
|
||||||
To create the tarball and the zip packages, you only need to tag a tag in your repository and push it to github:
|
|
||||||
|
|
||||||
```
|
|
||||||
git tag {version} -m "{ Description of this tag/version}"
|
|
||||||
git push --tags origin master
|
|
||||||
```
|
|
||||||
|
|
||||||
## Push to the testing/main pypi server
|
|
||||||
|
|
||||||
It is advisable that you try your package on the test repository and fix any problems first.
|
|
||||||
The process is simple:
|
|
||||||
```
|
|
||||||
python setup.py register -r {pypitest/pypi}
|
|
||||||
python setup.py sdist upload -r {pypitest/pypi}
|
|
||||||
```
|
|
||||||
|
|
||||||
If everything went as expected, you can now install your package through pip and browse your package's page.
|
|
||||||
For instance, check my senpy package: [https://pypi.python.org/pypi/senpy](https://pypi.python.org/pypi/senpy)
|
|
||||||
```
|
|
||||||
pip install senpy
|
|
||||||
```
|
|
@ -1,67 +0,0 @@
|
|||||||
---
|
|
||||||
layout: post
|
|
||||||
title: "Proxies with Apache and python"
|
|
||||||
date: 2014-10-09 10:00:00
|
|
||||||
tags: python apache proxy gunicorn uwsgi
|
|
||||||
---
|
|
||||||
|
|
||||||
This is a quick note on proxying a local python application (e.g. flask) to a subdirectory in Apache.
|
|
||||||
This assumes that the file wsgi.py contains a WSGI application with the name *application*. Hence, wsgi:application.
|
|
||||||
|
|
||||||
## Gunicorn
|
|
||||||
{% highlight apache %}
|
|
||||||
<Location /myapp/>
|
|
||||||
ProxyPass http://127.0.0.1:8888/myapp/
|
|
||||||
ProxyPassReverse http://127.0.0.1:8888/myapp/
|
|
||||||
RequestHeader set SCRIPT_NAME "/myapp/"
|
|
||||||
</Location>
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
**Important**: *SCRIPT_NAME* and the end of *ProxyPass* URL **MUST BE THE SAME**. Otherwise, Gunicorn will fail miserably.
|
|
||||||
|
|
||||||
Try it with:
|
|
||||||
{% highlight bash %}
|
|
||||||
venv/bin/gunicorn -w 4 -b 127.0.0.1:8888 --log-file - --access-logfile - wsgi:application
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
|
|
||||||
## UWSGI
|
|
||||||
This is a very simple configuration. I will try to upload one with more options for uwsgi (in a .ini file).
|
|
||||||
|
|
||||||
{% highlight apache %}
|
|
||||||
<Location /myapp/>
|
|
||||||
SetHandler uwsgi_handler
|
|
||||||
uWSGISocker 127.0.0.1:8888
|
|
||||||
</Location>
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
Try it with:
|
|
||||||
|
|
||||||
{% highlight bash %}
|
|
||||||
uwsgi --socket 127.0.0.1:8888 -w wsgi:application
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
### Extra: Supervisor
|
|
||||||
If everything went as expected, you can wrap your command in a supervisor config file and let it handle the server for you.
|
|
||||||
{% highlight ini %}
|
|
||||||
[unix_http_server]
|
|
||||||
file=/tmp/myapp.sock ; path to your socket file
|
|
||||||
|
|
||||||
[supervisord]
|
|
||||||
logfile = %(here)s/logs/supervisor.log
|
|
||||||
childlogdir = %(here)s/logs/
|
|
||||||
|
|
||||||
[rpcinterface:supervisor]
|
|
||||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
|
||||||
|
|
||||||
[supervisorctl]
|
|
||||||
logfile = %(here)s/logs/supervisorctl.log
|
|
||||||
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
|
|
||||||
|
|
||||||
|
|
||||||
[program:myapp]
|
|
||||||
command = venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 --log-file %(here)s/logs/gunicorn.log --access-logfile - wsgi:application
|
|
||||||
directory = %(here)s
|
|
||||||
environment = PATH=%(here)s/venv/bin/
|
|
||||||
logfile = %(here)s/logs/myapp.log
|
|
||||||
{% endhighlight %}
|
|
@ -1,78 +0,0 @@
|
|||||||
---
|
|
||||||
layout: post
|
|
||||||
title: "Zotero"
|
|
||||||
date: 2014-12-09 12:12:12
|
|
||||||
tags: zotero webdav nginx apache
|
|
||||||
---
|
|
||||||
[Zotero](https://www.zotero.org/) is an Open Source tool that lets you organise your bibliography, syncing it with the cloud.
|
|
||||||
Unlike other alternatives such as [Mendeley](http://www.mendeley.com), Zotero can upload the attachments and data to a private cloud via WebDav.
|
|
||||||
|
|
||||||
If you use nginx as your web server, know that even though it provides partial support for webdav, Zotero needs more than that.
|
|
||||||
Hence, you will need another webdav server, and optionally let nginx proxy to it.
|
|
||||||
This short post provides the basics to get that set-up working under Debian/Ubuntu.
|
|
||||||
|
|
||||||
## Setting up Apache
|
|
||||||
|
|
||||||
First we need to install Apache:
|
|
||||||
|
|
||||||
sudo apt-get install apache2
|
|
||||||
|
|
||||||
Change the head of "/etc/apache2/sites-enabled/000-default" to:
|
|
||||||
|
|
||||||
<VirtualHost *:880>
|
|
||||||
|
|
||||||
Then, create a file /etc/apache2/sites-available/webdav:
|
|
||||||
|
|
||||||
Alias /dav /home/webdav/dav
|
|
||||||
<Location /dav>
|
|
||||||
Dav on
|
|
||||||
Order Allow,Deny
|
|
||||||
Allow from all
|
|
||||||
Dav On
|
|
||||||
Options +Indexes
|
|
||||||
AuthType Basic
|
|
||||||
AuthName DAV
|
|
||||||
AuthBasicProvider file
|
|
||||||
AuthUserFile /home/webdav/.htpasswd
|
|
||||||
Require valid-user
|
|
||||||
</Location>
|
|
||||||
|
|
||||||
Ideally, you want your webdav folders to be private, adding authentication to them.
|
|
||||||
So you need to create the webdav and zotero users and add the passwords to an htpasswd file.
|
|
||||||
Even though you could use a single user, since you will be configuring several clients with your credentials I encourage you to create the zotero user as well.
|
|
||||||
This way you can always change the password for zotero without affecting any other application using webdav.
|
|
||||||
|
|
||||||
sudo adduser webdav
|
|
||||||
sudo htpasswd -c /home/webdav/.htpasswd webdav
|
|
||||||
sudo htpasswd /home/webdav/.htpasswd zotero
|
|
||||||
sudo mkdir -p /home/webdav/dav/zotero
|
|
||||||
|
|
||||||
Enable the site and restart apache:
|
|
||||||
|
|
||||||
sudo a2enmod webdav
|
|
||||||
sudo a2enmod dav_fs
|
|
||||||
sudo a2ensite webdav
|
|
||||||
sudo service apache2 restart
|
|
||||||
|
|
||||||
At this point everything should be working at http://\<your_host\>:880/dav/zotero
|
|
||||||
|
|
||||||
## Setting up NGINX
|
|
||||||
After the Apache side is working, we can use nginx as a proxy to get cleaner URIs.
|
|
||||||
In your desired site/location, add this:
|
|
||||||
|
|
||||||
location /dav {
|
|
||||||
client_max_body_size 20M;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $remote_addr;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_pass http://127.0.0.1:880;
|
|
||||||
}
|
|
||||||
|
|
||||||
Now just reload nginx:
|
|
||||||
|
|
||||||
sudo service nginx force-reload
|
|
||||||
|
|
||||||
## Extras
|
|
||||||
|
|
||||||
* [Zotero Reader](http://zoteroreader.com/) - HTML5 client
|
|
||||||
* [Zandy](https://github.com/ajlyon/zandy) - Android Open Source client
|
|
@ -221,31 +221,6 @@ header {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#headline {
|
|
||||||
margin: 0em;
|
|
||||||
font-size: 4em;
|
|
||||||
padding-left: 300px;
|
|
||||||
padding-top: 5px;
|
|
||||||
font-family: comfortaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
#headline a {
|
|
||||||
color: black;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#headline a:hover {
|
|
||||||
color: white;
|
|
||||||
text-shadow: 1px 1px #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#social {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*#social:after {*/
|
/*#social:after {*/
|
||||||
/*content: ".";*/
|
/*content: ".";*/
|
||||||
/*display: block;*/
|
/*display: block;*/
|
||||||
@ -283,16 +258,6 @@ header {
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entries dt {
|
|
||||||
font-weight: bold;
|
|
||||||
clear: both;
|
|
||||||
border-top: dashed 1px #CCC;
|
|
||||||
}
|
|
||||||
.entries dd {
|
|
||||||
float: right;
|
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navbar {
|
#navbar {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
padding-left: 300px;
|
padding-left: 300px;
|
||||||
@ -354,11 +319,6 @@ a {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #FFF;
|
|
||||||
text-shadow: 1px 1px #069, -1px -1px 0 #069, 1px -1px 0 #069, -1px 1px 0 #069;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
font-family: comfortaa;
|
font-family: comfortaa;
|
||||||
border-radius: 10px 0px 0px 10px;
|
border-radius: 10px 0px 0px 10px;
|
||||||
@ -397,7 +357,6 @@ a:hover {
|
|||||||
display:inline;
|
display:inline;
|
||||||
width: 33.333%;
|
width: 33.333%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@-webkit-keyframes toright {
|
@-webkit-keyframes toright {
|
||||||
from {
|
from {
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
@ -420,6 +379,28 @@ a:hover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes toleft {
|
||||||
|
to {
|
||||||
|
left: -10px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
from {
|
||||||
|
left: 300px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes toleft {
|
||||||
|
to {
|
||||||
|
left: -10px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
from {
|
||||||
|
left: 300px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@-webkit-keyframes appear{
|
@-webkit-keyframes appear{
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@ -438,27 +419,46 @@ a:hover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes disappear{
|
||||||
|
to {
|
||||||
|
opacity: 0 ;
|
||||||
|
}
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes disappear{
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#subheadline {
|
#subheadline {
|
||||||
-webkit-animation-name: toright;
|
position: absolute;
|
||||||
-webkit-animation-duration: 4s;
|
-webkit-animation-name: toleft;
|
||||||
|
-webkit-animation-duration: 3s;
|
||||||
-webkit-animation-iteration-count: 1;
|
-webkit-animation-iteration-count: 1;
|
||||||
-webkit-animation-timing-function: linear;
|
-webkit-animation-timing-function: linear;
|
||||||
-webkit-animation-fill-mode: forwards;
|
-webkit-animation-fill-mode: forwards;
|
||||||
animation-name: toright;
|
animation-name: toleft;
|
||||||
animation-duration: 4s;
|
animation-duration: 3s;
|
||||||
animation-iteration-count: 1;
|
animation-iteration-count: 1;
|
||||||
animation-timing-function: linear;
|
animation-timing-function: linear;
|
||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.disappear {
|
.disappear {
|
||||||
-webkit-animation-name: appear;
|
-webkit-animation-name: disappear;
|
||||||
-webkit-animation-duration: 4s;
|
-webkit-animation-duration: 3s;
|
||||||
-webkit-animation-timing-function: linear;
|
-webkit-animation-timing-function: linear;
|
||||||
-webkit-animation-iteration-count: 1;
|
-webkit-animation-iteration-count: 1;
|
||||||
-webkit-animation-fill-mode: forwards;
|
-webkit-animation-fill-mode: forwards;
|
||||||
animation-name: appear;
|
animation-name: disappear;
|
||||||
animation-duration: 4s;
|
animation-duration: 3s;
|
||||||
animation-iteration-count: 1;
|
animation-iteration-count: 1;
|
||||||
animation-timing-function: linear;
|
animation-timing-function: linear;
|
||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
@ -217,39 +217,9 @@ header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#headline {
|
#headline {
|
||||||
margin: 0em;
|
|
||||||
font-size: 4em;
|
|
||||||
padding-left: 20%;;
|
padding-left: 20%;;
|
||||||
padding-top: 5px;
|
|
||||||
font-family: comfortaa;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#headline a {
|
|
||||||
color: black;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#headline a:hover {
|
|
||||||
color: white;
|
|
||||||
text-shadow: 2px 2px #000, -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#social {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*#social:after {*/
|
|
||||||
/*content: ".";*/
|
|
||||||
/*display: block;*/
|
|
||||||
/*clear: both;*/
|
|
||||||
/*visibility: hidden;*/
|
|
||||||
/*line-height: 0;*/
|
|
||||||
/*height: 0;*/
|
|
||||||
/*}*/
|
|
||||||
|
|
||||||
#social li {
|
#social li {
|
||||||
position: relative;
|
position: relative;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
@ -278,15 +248,6 @@ header {
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entries dt {
|
|
||||||
font-weight: bold;
|
|
||||||
clear: both;
|
|
||||||
border-top: dashed 1px #CCC;
|
|
||||||
}
|
|
||||||
.entries dd {
|
|
||||||
float: right;
|
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navbar {
|
#navbar {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
@ -349,11 +310,6 @@ a {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #FFF;
|
|
||||||
text-shadow: 1px 1px #069, -1px -1px 0 #069, 1px -1px 0 #069, -1px 1px 0 #069;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
font-family: comfortaa;
|
font-family: comfortaa;
|
||||||
border-radius: 10px 0px 0px 10px;
|
border-radius: 10px 0px 0px 10px;
|
@ -10,7 +10,11 @@
|
|||||||
#headline {
|
#headline {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0.5em;
|
font-size: 2em;
|
||||||
|
padding: 0.1em;
|
||||||
|
margin: 0.1em;
|
||||||
|
font-family: comfortaa;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#headline a {
|
#headline a {
|
||||||
@ -104,11 +108,6 @@ a {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #FFF;
|
|
||||||
text-shadow: 1px 1px #069, -1px -1px 0 #069, 1px -1px 0 #069, -1px 1px 0 #069;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar li.active a:hover {
|
.navbar li.active a:hover {
|
||||||
color: #c00;
|
color: #c00;
|
||||||
}
|
}
|
||||||
@ -182,3 +181,7 @@ footer {
|
|||||||
/*top: -50px;*/
|
/*top: -50px;*/
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.highlighttable {
|
||||||
|
width: 100%;
|
||||||
|
}
|
@ -61,13 +61,14 @@ body {
|
|||||||
padding-left: 0.5em;
|
padding-left: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
.highlighttable {
|
||||||
display: block;
|
display: block;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
position: relative;
|
||||||
background-color: #EEE;
|
background-color: #EEE;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
padding: 1.5em 2em;
|
padding: 0;
|
||||||
border: dashed 1px #AAA;
|
border: dashed 1px #AAA;
|
||||||
border-radius: 5px 0px 5px 5px;
|
border-radius: 5px 0px 5px 5px;
|
||||||
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
||||||
@ -75,6 +76,25 @@ code {
|
|||||||
box-sizing: border-box; /* Opera/IE 8+ */
|
box-sizing: border-box; /* Opera/IE 8+ */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
/*background-color: #EEE;*/
|
||||||
|
overflow: auto;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em;
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linenos {
|
||||||
|
color: #AAA;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
border-right: solid 1px #DDD;
|
||||||
|
}
|
||||||
|
|
||||||
#navbar {
|
#navbar {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
@ -122,3 +142,67 @@ code {
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto
|
margin-right: auto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#headline {
|
||||||
|
position: relative;
|
||||||
|
margin: 0em;
|
||||||
|
font-size: 4em;
|
||||||
|
padding-left: 300px;
|
||||||
|
padding-top: 5px;
|
||||||
|
font-family: comfortaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headline a {
|
||||||
|
color: black;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headline a.inv {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 1px 1px #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headline a:hover {
|
||||||
|
color: white;
|
||||||
|
text-shadow: 1px 1px #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#social {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entries dt {
|
||||||
|
font-weight: bold;
|
||||||
|
clear: both;
|
||||||
|
border-top: dashed 1px #CCC;
|
||||||
|
}
|
||||||
|
.entries dd {
|
||||||
|
float: right;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-shadow: 0.5px 0.5px #CCC, -0.5px -0.5px 0 #CCC, 0.5px -0.5px 0 #CCC, -0.5px 0.5px 0 #CCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag:hover * {
|
||||||
|
-webkit-transform: rotate(-4deg);
|
||||||
|
-moz-transform: rotate(-4deg);
|
||||||
|
transform: rotate(-4deg);
|
||||||
|
color: black;
|
||||||
|
text-shadow: none ;
|
||||||
|
transform-origin: 95% 50%;
|
||||||
|
-moz-transform-origin: 95% 50%;
|
||||||
|
-webkit-transform-origin: 95% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag * {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hll {
|
||||||
|
background: yellow;
|
||||||
|
}
|
1
balkiantheme/static/css/solarized-dark.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
.highlight{background-color:#073642;color:#93a1a1}.highlight .c{color:#586e75 !important;font-style:italic !important}.highlight .cm{color:#586e75 !important;font-style:italic !important}.highlight .cp{color:#586e75 !important;font-style:italic !important}.highlight .c1{color:#586e75 !important;font-style:italic !important}.highlight .cs{color:#586e75 !important;font-weight:bold !important;font-style:italic !important}.highlight .err{color:#dc322f !important;background:none !important}.highlight .k{color:#cb4b16 !important}.highlight .o{color:#93a1a1 !important;font-weight:bold !important}.highlight .p{color:#93a1a1 !important}.highlight .ow{color:#2aa198 !important;font-weight:bold !important}.highlight .gd{color:#93a1a1 !important;background-color:#372c34 !important;display:inline-block}.highlight .gd .x{color:#93a1a1 !important;background-color:#4d2d33 !important;display:inline-block}.highlight .ge{color:#93a1a1 !important;font-style:italic !important}.highlight .gr{color:#aa0000}.highlight .gh{color:#586e75 !important}.highlight .gi{color:#93a1a1 !important;background-color:#1a412b !important;display:inline-block}.highlight .gi .x{color:#93a1a1 !important;background-color:#355720 !important;display:inline-block}.highlight .go{color:#888888}.highlight .gp{color:#555555}.highlight .gs{color:#93a1a1 !important;font-weight:bold !important}.highlight .gu{color:#6c71c4 !important}.highlight .gt{color:#aa0000}.highlight .kc{color:#859900 !important;font-weight:bold !important}.highlight .kd{color:#268bd2 !important}.highlight .kp{color:#cb4b16 !important;font-weight:bold !important}.highlight .kr{color:#d33682 !important;font-weight:bold !important}.highlight .kt{color:#2aa198 !important}.highlight .n{color:#268bd2 !important}.highlight .na{color:#268bd2 !important}.highlight .nb{color:#859900 !important}.highlight .nc{color:#d33682 !important}.highlight .no{color:#b58900 !important}.highlight .ni{color:#800080}.highlight .nl{color:#859900 !important}.highlight .ne{color:#268bd2 !important;font-weight:bold !important}.highlight .nf{color:#268bd2 !important;font-weight:bold !important}.highlight .nn{color:#b58900 !important}.highlight .nt{color:#268bd2 !important;font-weight:bold !important}.highlight .nx{color:#b58900 !important}.highlight .bp{color:#999999}.highlight .vc{color:#008080}.highlight .vg{color:#268bd2 !important}.highlight .vi{color:#268bd2 !important}.highlight .nv{color:#268bd2 !important}.highlight .w{color:#bbbbbb}.highlight .mf{color:#2aa198 !important}.highlight .m{color:#2aa198 !important}.highlight .mh{color:#2aa198 !important}.highlight .mi{color:#2aa198 !important}.highlight .mo{color:#009999}.highlight .s{color:#2aa198 !important}.highlight .sb{color:#d14}.highlight .sc{color:#d14}.highlight .sd{color:#2aa198 !important}.highlight .s2{color:#2aa198 !important}.highlight .se{color:#dc322f !important}.highlight .sh{color:#d14}.highlight .si{color:#268bd2 !important}.highlight .sx{color:#d14}.highlight .sr{color:#2aa198 !important}.highlight .s1{color:#2aa198 !important}.highlight .ss{color:#990073}.highlight .il{color:#009999}.highlight div .gd,.highlight div .gd .x,.highlight div .gi,.highlight div .gi .x{display:inline-block;width:100%}
|
69
balkiantheme/static/css/solarized.css
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
.hll { background-color: #ffffcc }
|
||||||
|
.c { color: #586E75 } /* Comment */
|
||||||
|
.err { color: #93A1A1 } /* Error */
|
||||||
|
.g { color: #93A1A1 } /* Generic */
|
||||||
|
.k { color: #859900 } /* Keyword */
|
||||||
|
.l { color: #93A1A1 } /* Literal */
|
||||||
|
.n { color: #93A1A1 } /* Name */
|
||||||
|
.o { color: #859900 } /* Operator */
|
||||||
|
.x { color: #CB4B16 } /* Other */
|
||||||
|
.p { color: #93A1A1 } /* Punctuation */
|
||||||
|
.cm { color: #586E75 } /* Comment.Multiline */
|
||||||
|
.cp { color: #859900 } /* Comment.Preproc */
|
||||||
|
.c1 { color: #586E75 } /* Comment.Single */
|
||||||
|
.cs { color: #859900 } /* Comment.Special */
|
||||||
|
.gd { color: #2AA198 } /* Generic.Deleted */
|
||||||
|
.ge { color: #93A1A1; font-style: italic } /* Generic.Emph */
|
||||||
|
.gr { color: #DC322F } /* Generic.Error */
|
||||||
|
.gh { color: #CB4B16 } /* Generic.Heading */
|
||||||
|
.gi { color: #859900 } /* Generic.Inserted */
|
||||||
|
.go { color: #93A1A1 } /* Generic.Output */
|
||||||
|
.gp { color: #93A1A1 } /* Generic.Prompt */
|
||||||
|
.gs { color: #93A1A1; font-weight: bold } /* Generic.Strong */
|
||||||
|
.gu { color: #CB4B16 } /* Generic.Subheading */
|
||||||
|
.gt { color: #93A1A1 } /* Generic.Traceback */
|
||||||
|
.kc { color: #CB4B16 } /* Keyword.Constant */
|
||||||
|
.kd { color: #268BD2 } /* Keyword.Declaration */
|
||||||
|
.kn { color: #859900 } /* Keyword.Namespace */
|
||||||
|
.kp { color: #859900 } /* Keyword.Pseudo */
|
||||||
|
.kr { color: #268BD2 } /* Keyword.Reserved */
|
||||||
|
.kt { color: #DC322F } /* Keyword.Type */
|
||||||
|
.ld { color: #93A1A1 } /* Literal.Date */
|
||||||
|
.m { color: #2AA198 } /* Literal.Number */
|
||||||
|
.s { color: #2AA198 } /* Literal.String */
|
||||||
|
.na { color: #93A1A1 } /* Name.Attribute */
|
||||||
|
.nb { color: #B58900 } /* Name.Builtin */
|
||||||
|
.nc { color: #268BD2 } /* Name.Class */
|
||||||
|
.no { color: #CB4B16 } /* Name.Constant */
|
||||||
|
.nd { color: #268BD2 } /* Name.Decorator */
|
||||||
|
.ni { color: #CB4B16 } /* Name.Entity */
|
||||||
|
.ne { color: #CB4B16 } /* Name.Exception */
|
||||||
|
.nf { color: #268BD2 } /* Name.Function */
|
||||||
|
.nl { color: #93A1A1 } /* Name.Label */
|
||||||
|
.nn { color: #93A1A1 } /* Name.Namespace */
|
||||||
|
.nx { color: #93A1A1 } /* Name.Other */
|
||||||
|
.py { color: #93A1A1 } /* Name.Property */
|
||||||
|
.nt { color: #268BD2 } /* Name.Tag */
|
||||||
|
.nv { color: #268BD2 } /* Name.Variable */
|
||||||
|
.ow { color: #859900 } /* Operator.Word */
|
||||||
|
.w { color: #93A1A1 } /* Text.Whitespace */
|
||||||
|
.mf { color: #2AA198 } /* Literal.Number.Float */
|
||||||
|
.mh { color: #2AA198 } /* Literal.Number.Hex */
|
||||||
|
.mi { color: #2AA198 } /* Literal.Number.Integer */
|
||||||
|
.mo { color: #2AA198 } /* Literal.Number.Oct */
|
||||||
|
.sb { color: #586E75 } /* Literal.String.Backtick */
|
||||||
|
.sc { color: #2AA198 } /* Literal.String.Char */
|
||||||
|
.sd { color: #93A1A1 } /* Literal.String.Doc */
|
||||||
|
.s2 { color: #2AA198 } /* Literal.String.Double */
|
||||||
|
.se { color: #CB4B16 } /* Literal.String.Escape */
|
||||||
|
.sh { color: #93A1A1 } /* Literal.String.Heredoc */
|
||||||
|
.si { color: #2AA198 } /* Literal.String.Interpol */
|
||||||
|
.sx { color: #2AA198 } /* Literal.String.Other */
|
||||||
|
.sr { color: #DC322F } /* Literal.String.Regex */
|
||||||
|
.s1 { color: #2AA198 } /* Literal.String.Single */
|
||||||
|
.ss { color: #2AA198 } /* Literal.String.Symbol */
|
||||||
|
.bp { color: #268BD2 } /* Name.Builtin.Pseudo */
|
||||||
|
.vc { color: #268BD2 } /* Name.Variable.Class */
|
||||||
|
.vg { color: #268BD2 } /* Name.Variable.Global */
|
||||||
|
.vi { color: #268BD2 } /* Name.Variable.Instance */
|
||||||
|
.il { color: #2AA198 } /* Literal.Number.Integer.Long */
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 193 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 194 KiB |
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 218 KiB |
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 207 KiB |
22
balkiantheme/templates/article.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="postnav">
|
||||||
|
{% if article.prev_article %}
|
||||||
|
<a href="{{ article.prev_article.url }}"><span class="previouspost"><i class="icon-chevron-sign-left"></i> {{ article.prev_article.title }}</a></span>
|
||||||
|
{% endif %}
|
||||||
|
{% if article.next_article %}
|
||||||
|
<span class='nextpost'><a href="{{ SITE_URL }}/{{ article.next_article.url }}">{{ article.next_article.title }} <i class="icon-chevron-sign-right"></i></span></a>
|
||||||
|
{%endif%}
|
||||||
|
</div>
|
||||||
|
<div class="posthead">
|
||||||
|
<h2 class="title">{{ article.title }}</h2>
|
||||||
|
<span class="meta date">{{ article.date | date_to_string }}</span>
|
||||||
|
{% for c in article.tags %}
|
||||||
|
<a class="tag" href="/tag/{{ c }}.html"><span class="label label-default">{{ c }}</span></a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="post">
|
||||||
|
{{ article.content }}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -3,38 +3,43 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
<title>{{ page.title }}</title>
|
<title>{% block content_title %}{% endblock %}</title>
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width">
|
||||||
|
|
||||||
<!-- syntax highlighting CSS -->
|
<!-- syntax highlighting CSS -->
|
||||||
<link rel="stylesheet" href="/css/syntax.css">
|
<link rel="stylesheet" href="{{ SITE_URL }}/theme/css/solarized-dark.css">
|
||||||
<!--<link href="/css/bootstrap.css" rel="stylesheet">-->
|
<!--<link href="/css/bootstrap.css" rel="stylesheet">-->
|
||||||
<link rel="stylesheet" href="/font-awesome/css/font-awesome.min.css">
|
<link rel="stylesheet" href="{{ SITE_URL }}/theme/font-awesome/css/font-awesome.min.css">
|
||||||
|
|
||||||
<!--<link rel="stylesheet" href="/css/bootstrap-responsive.min.css">-->
|
<!--<link rel="stylesheet" href="/css/bootstrap-responsive.min.css">-->
|
||||||
|
|
||||||
<!-- Custom CSS -->
|
<!-- Custom CSS -->
|
||||||
<link rel="stylesheet" media="only screen" href="/css/main.css">
|
<link rel="stylesheet" media="only screen" href="{{ SITE_URL }}/theme/css/main.css">
|
||||||
<link rel="stylesheet" media="only screen and (min-width: 0px) and (max-width: 599px)" href="/css/main-xs.css">
|
<link rel="stylesheet" media="only screen and (min-width: 0px) and (max-width: 599px)" href="{{ SITE_URL }}/theme/css/main-xs.css">
|
||||||
<link rel="stylesheet" media="only screen and (min-width: 600px) and (max-width: 1199px)" href="/css/main-medium.css">
|
<link rel="stylesheet" media="only screen and (min-width: 600px) and (max-width: 1199px)" href="{{ SITE_URL }}/theme/css/main-medium.css">
|
||||||
<link rel="stylesheet" media="only screen and (min-width: 1200px)" href="/css/main-desktop.css">
|
<link rel="stylesheet" media="only screen and (min-width: 1200px)" href="{{ SITE_URL }}/theme/css/main-desktop.css">
|
||||||
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300|Comfortaa' rel='stylesheet' type='text/css'>
|
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300|Comfortaa' rel='stylesheet' type='text/css'>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container" class="container">
|
<div id="container" class="container">
|
||||||
<header id="header">
|
<header id="header">
|
||||||
<h1 id="headline"><a href="/">balkian</a><a href="/">.com</a></h1>
|
<h1 id="headline"><a href="/">balkian</a><a class="inv" href="/">.com</a></h1>
|
||||||
<div id="navbar" class="navbar navbar-inverse navbar-static-bottom">
|
<div id="navbar" class="navbar navbar-inverse navbar-static-bottom">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li {% if page.categories contains "blog" %}class="active" {% endif %}>
|
<li {% if article or not page %}class="active" {% endif %}>
|
||||||
<a href="/"><i class="icon-home icon-large"></i> Blog</a>
|
<a href="/"><i class="icon-home icon-large"></i> Blog</a>
|
||||||
</li>
|
</li>
|
||||||
{% for p in site.pages reversed %} {% if p.url contains "/page" %} {% else %} {% if p.url != "/index.html" %}
|
{% for p in pages %} {% if "/page" in p.url %}
|
||||||
<li {% if p.url == page.url %} class="active" {% else %} {% if page.categories contains p.categories %} class="active" {% endif %} {% endif %} >
|
{% else %}
|
||||||
<a href="{{ p.url | remove: "/index.html" }}">{{ p.title }}</a>
|
{% if p.url != "/index.html" %}
|
||||||
</li> {% endif %} {% endif %}
|
<li {% if page and p.url == page.url %} class="active"
|
||||||
|
{% endif %} >
|
||||||
|
<a href="/{{ p.url }}">{{ p.title }}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<!--<li class="dropdown">-->
|
<!--<li class="dropdown">-->
|
||||||
<!--<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown <b class="caret"></b></a>-->
|
<!--<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown <b class="caret"></b></a>-->
|
||||||
@ -58,21 +63,21 @@
|
|||||||
<div class="flipper sticky">
|
<div class="flipper sticky">
|
||||||
<div class="front">
|
<div class="front">
|
||||||
<!-- front content -->
|
<!-- front content -->
|
||||||
<img id="avatar" width=100% src="/img/me.png">
|
<img id="avatar" width=100% src="{{ SITE_URL }}/theme/img/me.png">
|
||||||
</div>
|
</div>
|
||||||
<div class="back">
|
<div class="back">
|
||||||
<!-- back content -->
|
<!-- back content -->
|
||||||
<img id="picture" width=100% src="/img/me.jpg">
|
<img id="picture" width=100% src="{{ SITE_URL }}/theme/img/me.jpg">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="entries">
|
<div class="entries">
|
||||||
<h1 class="title">Latest entries</h1>
|
<h1 class="title">Latest entries</h1>
|
||||||
<dl>
|
<dl>
|
||||||
{% for p in site.posts limit: 5 %}
|
{% for p in articles %}
|
||||||
<dt><a href="{{ p.url }}">{{ p.title }}</a></dt>
|
<dt><a href="{{ SITE_URL }}/{{ p.url }}">{{ p.title }}</a></dt>
|
||||||
{% for c in p.tags %}
|
{% for c in p.tags %}
|
||||||
<dd class="label label-default">{{ c }}</dd>
|
<a class="tag" href="/tag/{{ c }}.html"><dd class="label label-default">{{ c }}</dd></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</dl>
|
</dl>
|
||||||
@ -81,7 +86,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<!--Body content-->
|
<!--Body content-->
|
||||||
{{ content }}
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -105,7 +111,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/jquery-2.0.2.min.js"></script>
|
<script src="{{ SITE_URL }}/theme/js/jquery-2.0.2.min.js"></script>
|
||||||
<!--<script src="/js/bootstrap.min.js"></script>-->
|
<!--<script src="/js/bootstrap.min.js"></script>-->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
52
balkiantheme/templates/index.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
<!-- Pagination links -->
|
||||||
|
{% block content %}
|
||||||
|
{% if articles_paginator.num_pages > 0 %}
|
||||||
|
<div class="pagination pag-top">
|
||||||
|
{% if articles_page.has_previous() %}
|
||||||
|
<span class="previouspage"><i class="icon-chevron-sign-left"></i><a href="/{% if articles_page.has_previous() > 2 %}page{{ articles_page.previous_page_number() }}{% endif %}"> Newer Posts</a></span>
|
||||||
|
{% else %}
|
||||||
|
<span class="previouspage" style="visibility:hidden;"><i class="icon-chevron-sign-left"></i> Newer Posts</span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="page_number ">Page {{ articles_page.number }} of {{ articles_paginator.num_pages }}</span>
|
||||||
|
{% if articles_page.has_next() %}
|
||||||
|
<span class="nextpage"><a href="/page{{ articles_page.next_page_number() }}"> Older Posts </a> <i class="icon-chevron-sign-right"></i></span>
|
||||||
|
{% else %}
|
||||||
|
<span class="nextpage" style="visibility:hidden;">Older Posts <i class="icon-chevron-sign-right"></i></span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block pre_articles %}
|
||||||
|
{% endblock %}
|
||||||
|
<!-- This loops through the paginated posts -->
|
||||||
|
{% for post in articles_page.object_list %}
|
||||||
|
<div class="posthead">
|
||||||
|
<h2><a href="{{ SITE_URL }}/{{ post.url }}" class="title">{{ post.title }}</a></h2>
|
||||||
|
<span class="date">{{ post.date | date_to_string }}</span>
|
||||||
|
{% for c in post.tags %}
|
||||||
|
<a class="tag" href="{{ SITE_URL }}/tag/{{ c }}.html"><span class="label label-success tag">{{ c }}</span></a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="excerpt">
|
||||||
|
{{ post.summary }}
|
||||||
|
</div>
|
||||||
|
<span><a href="{{ SITE_URL }}/{{ post.url }}"><i class="icon-pl2s"></i> Read more...</a></span>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if articles_paginator.num_pages > 0 %}
|
||||||
|
<div class="pagination pag-bottom">
|
||||||
|
{% if articles_page.has_previous() %}
|
||||||
|
<span class="previouspage"><i class="icon-chevron-sign-left"></i><a href="/{% if articles_page.next_page_number() > 2 %}page{{ articles_page.previous_page_number }}{% endif %}"> Newer Posts</a></span>
|
||||||
|
{% else %}
|
||||||
|
<span class="previouspage" style="display:none;"><i class="icon-chevron-sign-left"></i> Newer Posts</span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="page_number ">Page {{ articles_page.number }} of {{ articles_paginator.num_pages }}</span>
|
||||||
|
{% if articles_page.has_next() %}
|
||||||
|
<span class="nextpage"><a href="/page{{ articles_page.next_page_number() }}"> Older Posts </a> <i class="icon-chevron-sign-right"></i></span>
|
||||||
|
{% else %}
|
||||||
|
<span class="nextpage" style="display:none;">Older Posts <i class="icon-chevron-sign-right"></i></span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
4
balkiantheme/templates/page.html
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{{ page.content }}
|
||||||
|
{% endblock %}
|
5
balkiantheme/templates/tag.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{% extends "index.html" %}
|
||||||
|
{% block title %}{{ SITENAME }} - {{ tag }}{% endblock %}
|
||||||
|
{% block pre_articles %}
|
||||||
|
<h1>Entries tagged: {{ tag }}</h1>
|
||||||
|
{% endblock %}
|
44
content/2013-08-17-creating-my-web.rst
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
Creating my web
|
||||||
|
###############
|
||||||
|
:date: 2013-08-22 14:14:22
|
||||||
|
:tags: starters, javascript, ruby, github, git
|
||||||
|
|
||||||
|
Finally, I've decided to set up a decent personal page. I have settled
|
||||||
|
for github-pages because I like the idea of keeping my site in a
|
||||||
|
repository and having someone else host and deploy it for me. The site
|
||||||
|
will be really simple, mostly static files. Thanks to Github,
|
||||||
|
`Jekyll <http://jekyllrb.com>`__ will automatically generate static
|
||||||
|
pages for my posts every time I commit anything new to this repository.
|
||||||
|
|
||||||
|
But Jekyll can be used independently, so if I ever choose to host the
|
||||||
|
site myself, I can do it quite easily. Another thing that I liked about
|
||||||
|
this approach is that the generated html files can be used in the
|
||||||
|
future, and I will not need Jekyll to serve it. Jekyll is really simple
|
||||||
|
and most of the things are written in plain html. That means that
|
||||||
|
everything could be easily reused if I ever choose to change to another
|
||||||
|
blogging framework (e.g. pelical). But, for the time being, I like the
|
||||||
|
fact that Github takes care of the compilation as well, so I can simply
|
||||||
|
modify or add files through the web interface should I need to.
|
||||||
|
|
||||||
|
I hadn't played with HTML and CSS for a while now, so I also wanted to
|
||||||
|
use this site as a playground. At some point, I realised I was doing
|
||||||
|
mostly everything in plain HTML and CSS, and decided to keep it like
|
||||||
|
that for as long as possible. As of this writing, I haven't included any
|
||||||
|
Javascript code in the page. Probably I will use some to add my
|
||||||
|
`gists <http://gist.github.com/balkian>`__ and
|
||||||
|
`repositories <http://github.com/balkian>`__, but we will see about
|
||||||
|
that.
|
||||||
|
|
||||||
|
I think the code speaks for itself, so you can check out `my repository
|
||||||
|
on Github <http://github.com/balkian/balkian.github.com>`__. You can
|
||||||
|
clone and deploy it easily like this:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
git clone
|
||||||
|
https://github.com/balkian/balkian.github.com cd balkian.github.com
|
||||||
|
jekyll serve -w
|
||||||
|
|
||||||
|
I will keep updating this post with information about: \* Some Jekyll
|
||||||
|
plugins that might be useful \* What CSS tricks I learnt \* The webfonts
|
||||||
|
I used \* The badge on the left side of the page
|
12
content/2013-08-22-Remove-git-files-with-globbing.rst
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Remove git files with globbing
|
||||||
|
##############################
|
||||||
|
:date: 2013-08-22 23:14:00
|
||||||
|
:tags: git
|
||||||
|
|
||||||
|
A simple trick. If you want to remove all the '.swp' files from a git
|
||||||
|
repository, just use:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
git rm --cached '\*\*.swp'
|
||||||
|
|
121
content/2014-03-27-updating-eurolovemap.rst
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
Updating EuroLoveMap
|
||||||
|
####################
|
||||||
|
:date: 2014-03-27 14:00:00
|
||||||
|
:tags: javascript, python, heroku
|
||||||
|
|
||||||
|
As part of the `OpeNER
|
||||||
|
hackathon <http://www.opener-project.org/2013/07/18/opener-hackathon-in-amsterdam/>`__
|
||||||
|
we decided to build a prototype that would allow us to compare how
|
||||||
|
different countries feel about several topics. We used the OpeNER
|
||||||
|
pipeline to get the sentiment from a set of newspaper articles we
|
||||||
|
gathered from media in several languages. Then we aggregated those
|
||||||
|
articles by category and country (using the source of the article or the
|
||||||
|
language it was written in), obtaining the "overall feeling" of each
|
||||||
|
country about each topic. Then, we used some fancy JavaScript to make
|
||||||
|
sense out of the raw information.
|
||||||
|
|
||||||
|
It didn't go too bad, it turns out `we
|
||||||
|
won <http://eurosentiment.eu/wp-content/uploads/2013/07/BOLv9qnCIAAJEek.jpg>`__.
|
||||||
|
|
||||||
|
Now, it was time for a face-lift. I used this opportunity to play with
|
||||||
|
new technologies and improve it:
|
||||||
|
|
||||||
|
- Using Flask, this time using python 3.3 and Bootstrap 3.0
|
||||||
|
- Cool HTML5+JS cards (thanks to
|
||||||
|
`pastetophone <http://pastetophone.com>`__)
|
||||||
|
- Automatic generation of fake personal data to test the interface
|
||||||
|
- Obfuscation of personal emails
|
||||||
|
|
||||||
|
Publishing a Python 3 app on Heroku
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
`seen here <http://eurolovemap.herokuapp.com/>`__
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
mkvirtualenv -p /usr/bin/python3.3 eurolovemap
|
||||||
|
|
||||||
|
Since Heroku uses python 2.7 by default, we have to tell it which
|
||||||
|
version we want, although it supports python 3.4 as well. I couldn't get
|
||||||
|
python 3.4 working using the
|
||||||
|
`deadsnakes <https://launchpad.net/~fkrull/+archive/deadsnakes>`__ ppa,
|
||||||
|
so I used python 3.3 instead, which works fine but is not officially
|
||||||
|
supported. Just create a file named *runtime.txt* in your project root,
|
||||||
|
with the python version you want to use:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python-3.3.1
|
||||||
|
|
||||||
|
Don't forget to freeze your dependencies so Heroku can install them:
|
||||||
|
``bash pip freze > requirements.txt``
|
||||||
|
|
||||||
|
Publishing personal emails
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
There are really sophisticated and effective ways to obfuscate personal
|
||||||
|
emails so that spammers cannot easily grab yours. However, this time I
|
||||||
|
needed something really simple to hide our emails from the simplest form
|
||||||
|
of crawlers. Most of the team are in academia somehow, so in the end all
|
||||||
|
our emails are available in sites like Google Scholar. Anyway, nobody
|
||||||
|
likes getting spammed so I settled for a custom `Caesar
|
||||||
|
cipher <http://en.wikipedia.org/wiki/Caesar_cipher>`__. Please, don't
|
||||||
|
use it for any serious application if you are concerned about being
|
||||||
|
spammed.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def blur_email(email):
|
||||||
|
return "".join([chr(ord(i)+5) for i in email])
|
||||||
|
|
||||||
|
And this is the client side:
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
window.onload = function(){
|
||||||
|
elems = document.getElementsByClassName('profile-email');
|
||||||
|
for(var e in elems){
|
||||||
|
var blur = elems[e].innerHTML;
|
||||||
|
var email = "";
|
||||||
|
for(var s in blur){
|
||||||
|
var a = blur.charCodeAt(s)
|
||||||
|
email = email+String.fromCharCode(a-5);
|
||||||
|
}
|
||||||
|
elems[e].innerHTML = email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Unfortunately, this approach does not hide your email from anyone using
|
||||||
|
`PhantomJS <http://phantomjs.org/>`__,
|
||||||
|
`ZombieJS <http://zombie.labnotes.org/>`__ or similar. For that, other
|
||||||
|
approaches like generating a picture with the address would be
|
||||||
|
necessary. Nevertheless, it is overkill for a really simple ad-hoc
|
||||||
|
application with custom formatting and just a bunch of emails that would
|
||||||
|
easily be grabbed manually.
|
||||||
|
|
||||||
|
Generation of fake data
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
To test the contact section of the site, I wanted to populate it with
|
||||||
|
fake data. `Fake-Factory <https://github.com/joke2k/faker>`__ is an
|
||||||
|
amazing library that can generate fake data of almost any kind: emails,
|
||||||
|
association names, acronyms... It even lets you localise the results
|
||||||
|
(get Spanish names, for instance) and generate factories for certain
|
||||||
|
classes (à la Django).
|
||||||
|
|
||||||
|
But I also wanted pictures, enter `Lorem
|
||||||
|
Pixel <http://lorempixel.com/>`__. With its API you can generate
|
||||||
|
pictures of almost any size, for different topics (e.g. nightlife,
|
||||||
|
people) and with a custom text. You can even use an index, so it will
|
||||||
|
always show the same picture.
|
||||||
|
|
||||||
|
For instance, the picture below is served through Lorem Pixel.
|
||||||
|
|
||||||
|
.. figure:: http://lorempixel.com/400/200/nightlife/
|
||||||
|
:alt: This picture is generated with LoremIpsum
|
||||||
|
|
||||||
|
This picture is generated with LoremIpsum
|
||||||
|
|
||||||
|
By the way, if you only want cat pictures, take a look at
|
||||||
|
`Placekitten <http://placekitten.com/>`__. And for NSFW text, there's
|
||||||
|
the `Samuel L. Jackson Ipsum <http://slipsum.com/>`__
|
106
content/2014-09-23-publishing-in-pypi.rst
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
Publishing in PyPi
|
||||||
|
##################
|
||||||
|
:date: 2014-09-27 10:00:00
|
||||||
|
:tags: github, python, pypi
|
||||||
|
|
||||||
|
Developing a python module and publishing it on Github is cool, but most
|
||||||
|
of the times you want others to download and use it easily. That is the
|
||||||
|
role of PyPi, the python package repository. In this post I show you how
|
||||||
|
to publish your package in less than 10 minutes.
|
||||||
|
|
||||||
|
Choose a fancy name
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
If you haven't done so yet, take a minute or two to think about this. To
|
||||||
|
publish on PyPi you need a name for your package that isn't taken.
|
||||||
|
What's more, a catchy and unique name will help people remember your
|
||||||
|
module and feel more inclined to at least try it.
|
||||||
|
|
||||||
|
The package name should hint what your module does, but that's not
|
||||||
|
always the case. That's your call. I personally put uniqueness and
|
||||||
|
memorability over describing the functionality.
|
||||||
|
|
||||||
|
Create a .pypirc configuration file
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. code:: cfg
|
||||||
|
|
||||||
|
[distutils] # this tells distutils what package indexes you can push to
|
||||||
|
index-servers =
|
||||||
|
pypi # the live PyPI
|
||||||
|
pypitest # test PyPI
|
||||||
|
|
||||||
|
[pypi] # authentication details for live PyPI
|
||||||
|
repository = https://pypi.python.org/pypi
|
||||||
|
username = { your_username }
|
||||||
|
password = { your_password } # not necessary
|
||||||
|
|
||||||
|
[pypitest] # authentication details for test PyPI
|
||||||
|
repository = https://testpypi.python.org/pypi
|
||||||
|
username = { your_username }
|
||||||
|
|
||||||
|
As you can see, you need to register both in the `main pypi
|
||||||
|
repository <https://pypi.python.org/pypi?%3Aaction=register_form>`__ and
|
||||||
|
the `testing
|
||||||
|
server <https://testpypi.python.org/pypi?%3Aaction=register_form>`__.
|
||||||
|
The usernames and passwords might be different, that is up to you!
|
||||||
|
|
||||||
|
Prepare your package
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
root-dir/ # Any name you want
|
||||||
|
setup.py
|
||||||
|
setup.cfg
|
||||||
|
LICENSE.txt
|
||||||
|
README.md
|
||||||
|
mypackage/
|
||||||
|
__init__.py
|
||||||
|
foo.py
|
||||||
|
bar.py
|
||||||
|
baz.py
|
||||||
|
|
||||||
|
setup.cfg
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
.. code:: cfg
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
description-file = README.md
|
||||||
|
|
||||||
|
The markdown README is the *de facto* standard in Github, but you can
|
||||||
|
also use rST (reStructuredText), the standard in the python community.
|
||||||
|
|
||||||
|
setup.py
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
{% highlight python %} from distutils.core import setup setup( name =
|
||||||
|
'mypackage', packages = ['mypackage'], # this must be the same as the
|
||||||
|
name above version = '{ version }', description = '{ description }',
|
||||||
|
author = '{ name }', author\_email = '{ email }', url =
|
||||||
|
'https://github.com/{user}/{package}', # URL to the github repo
|
||||||
|
download\_url = 'https://github.com/{user}/{repo}/tarball/{version}',
|
||||||
|
keywords = ['websockets', 'display', 'd3'], # list of keywords that
|
||||||
|
represent your package classifiers = [], ) {% endhighlight %}
|
||||||
|
|
||||||
|
You might notice that the download\_url points to a Github URL. We could
|
||||||
|
host our package anywhere, but Github is a convenient option. To create
|
||||||
|
the tarball and the zip packages, you only need to tag a tag in your
|
||||||
|
repository and push it to github:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
git tag {version} -m "{ Description of this tag/version}"
|
||||||
|
git push --tags origin master
|
||||||
|
|
||||||
|
Push to the testing/main pypi server
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
It is advisable that you try your package on the test repository and fix
|
||||||
|
any problems first. The process is simple:
|
||||||
|
``python setup.py register -r {pypitest/pypi} python setup.py sdist upload -r {pypitest/pypi}``
|
||||||
|
|
||||||
|
If everything went as expected, you can now install your package through
|
||||||
|
pip and browse your package's page. For instance, check my senpy
|
||||||
|
package: https://pypi.python.org/pypi/senpy ``pip install senpy``
|
72
content/2014-10-09-proxies.rst
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
Proxies with Apache and python
|
||||||
|
##############################
|
||||||
|
:date: 2014-10-09 10:00:00
|
||||||
|
:tags: python, apache, proxy, gunicorn, uwsgi
|
||||||
|
|
||||||
|
This is a quick note on proxying a local python application (e.g. flask)
|
||||||
|
to a subdirectory in Apache. This assumes that the file wsgi.py contains
|
||||||
|
a WSGI application with the name *application*. Hence, wsgi:application.
|
||||||
|
|
||||||
|
Gunicorn
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. code-block:: apache
|
||||||
|
|
||||||
|
<Location /myapp/>
|
||||||
|
ProxyPass http://127.0.0.1:8888/myapp/
|
||||||
|
ProxyPassReverse http://127.0.0.1:8888/myapp/
|
||||||
|
RequestHeader set SCRIPT_NAME "/myapp/"
|
||||||
|
</Location>
|
||||||
|
|
||||||
|
**Important**: *SCRIPT\_NAME* and the end of *ProxyPass* URL **MUST BE
|
||||||
|
THE SAME**. Otherwise, Gunicorn will fail miserably.
|
||||||
|
|
||||||
|
Try it with:
|
||||||
|
``bash venv/bin/gunicorn -w 4 -b 127.0.0.1:8888 --log-file - --access-logfile - wsgi:application``
|
||||||
|
|
||||||
|
UWSGI
|
||||||
|
-----
|
||||||
|
|
||||||
|
This is a very simple configuration. I will try to upload one with more
|
||||||
|
options for uwsgi (in a .ini file).
|
||||||
|
|
||||||
|
.. code-block:: apache
|
||||||
|
|
||||||
|
<Location /myapp/>
|
||||||
|
SetHandler uwsgi_handler
|
||||||
|
uWSGISocker 127.0.0.1:8888
|
||||||
|
</Location>
|
||||||
|
|
||||||
|
Try it with:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
uwsgi --socket 127.0.0.1:8888 -w wsgi:application
|
||||||
|
|
||||||
|
Extra: Supervisor
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If everything went as expected, you can wrap your command in a
|
||||||
|
supervisor config file and let it handle the server for you.
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[unix_http_server]
|
||||||
|
file=/tmp/myapp.sock ; path to your socket file
|
||||||
|
|
||||||
|
[supervisord]
|
||||||
|
logfile = %(here)s/logs/supervisor.log
|
||||||
|
childlogdir = %(here)s/logs/
|
||||||
|
|
||||||
|
[rpcinterface:supervisor]
|
||||||
|
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||||
|
|
||||||
|
[supervisorctl]
|
||||||
|
logfile = %(here)s/logs/supervisorctl.log
|
||||||
|
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
|
||||||
|
|
||||||
|
[program:myapp]
|
||||||
|
command = venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 --log-file %(here)s/logs/gunicorn.log --access-logfile - wsgi:application
|
||||||
|
directory = %(here)s
|
||||||
|
environment = PATH=%(here)s/venv/bin/
|
||||||
|
logfile = %(here)s/logs/myapp.log
|
105
content/2014-12-09-zotero.rst
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
Zotero
|
||||||
|
######
|
||||||
|
:date: 2014-12-09 12:12:12
|
||||||
|
:tags: zotero, webdav, nginx, apache
|
||||||
|
|
||||||
|
`Zotero <https://www.zotero.org/>`__ is an Open Source tool that lets
|
||||||
|
you organise your bibliography, syncing it with the cloud. Unlike other
|
||||||
|
alternatives such as `Mendeley <http://www.mendeley.com>`__, Zotero can
|
||||||
|
upload the attachments and data to a private cloud via WebDav.
|
||||||
|
|
||||||
|
If you use nginx as your web server, know that even though it provides
|
||||||
|
partial support for webdav, Zotero needs more than that. Hence, you will
|
||||||
|
need another webdav server, and optionally let nginx proxy to it. This
|
||||||
|
short post provides the basics to get that set-up working under
|
||||||
|
Debian/Ubuntu.
|
||||||
|
|
||||||
|
Setting up Apache
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
First we need to install Apache:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
sudo apt-get install apache2
|
||||||
|
|
||||||
|
Change the head of "/etc/apache2/sites-enabled/000-default" to:
|
||||||
|
|
||||||
|
.. code-block:: apache
|
||||||
|
|
||||||
|
<VirtualHost *:880>
|
||||||
|
|
||||||
|
Then, create a file /etc/apache2/sites-available/webdav:
|
||||||
|
|
||||||
|
.. code-block:: apache
|
||||||
|
|
||||||
|
Alias /dav /home/webdav/dav
|
||||||
|
<Location /dav>
|
||||||
|
Dav on
|
||||||
|
Order Allow,Deny
|
||||||
|
Allow from all
|
||||||
|
Dav On
|
||||||
|
Options +Indexes
|
||||||
|
AuthType Basic
|
||||||
|
AuthName DAV
|
||||||
|
AuthBasicProvider file
|
||||||
|
AuthUserFile /home/webdav/.htpasswd
|
||||||
|
Require valid-user
|
||||||
|
</Location>
|
||||||
|
|
||||||
|
Ideally, you want your webdav folders to be private, adding
|
||||||
|
authentication to them. So you need to create the webdav and zotero
|
||||||
|
users and add the passwords to an htpasswd file. Even though you could
|
||||||
|
use a single user, since you will be configuring several clients with
|
||||||
|
your credentials I encourage you to create the zotero user as well. This
|
||||||
|
way you can always change the password for zotero without affecting any
|
||||||
|
other application using webdav.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
sudo adduser webdav
|
||||||
|
sudo htpasswd -c /home/webdav/.htpasswd webdav
|
||||||
|
sudo htpasswd /home/webdav/.htpasswd zotero
|
||||||
|
sudo mkdir -p /home/webdav/dav/zotero
|
||||||
|
|
||||||
|
Enable the site and restart apache:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
sudo a2enmod webdav
|
||||||
|
sudo a2enmod dav_fs
|
||||||
|
sudo a2ensite webdav
|
||||||
|
sudo service apache2 restart
|
||||||
|
|
||||||
|
At this point everything should be working at
|
||||||
|
http://<your\_host>:880/dav/zotero
|
||||||
|
|
||||||
|
Setting up NGINX
|
||||||
|
----------------
|
||||||
|
|
||||||
|
After the Apache side is working, we can use nginx as a proxy to get
|
||||||
|
cleaner URIs. In your desired site/location, add this:
|
||||||
|
|
||||||
|
.. code-block:: nginx
|
||||||
|
|
||||||
|
location /dav {
|
||||||
|
client_max_body_size 20M;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_pass http://127.0.0.1:880;
|
||||||
|
}
|
||||||
|
|
||||||
|
Now just reload nginx:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
sudo service nginx force-reload
|
||||||
|
|
||||||
|
Extras
|
||||||
|
------
|
||||||
|
|
||||||
|
- `Zotero Reader <http://zoteroreader.com/>`__ - HTML5 client
|
||||||
|
- `Zandy <https://github.com/ajlyon/zandy>`__ - Android Open Source
|
||||||
|
client
|
||||||
|
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
@ -1,9 +1,6 @@
|
|||||||
---
|
|
||||||
layout: default
|
|
||||||
title: About
|
title: About
|
||||||
---
|
|
||||||
<h2>This is me<span class="disappear">...</span></h2>
|
<h2>This is <span class="disappear">me.</span><span id="subheadline"><span class="disappear">... I mean,</span> my website</span></h2>
|
||||||
<h3 id="subheadline">... I mean, my website</h3>
|
|
||||||
|
|
||||||
<div id="about">
|
<div id="about">
|
||||||
<ul>
|
<ul>
|
103
content/pages/cv.md
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
title: CV
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="stylesheet" media="only screen and (min-width: 0px) and (max-width: 599)" href="/theme/css/cv-xs.css">
|
||||||
|
<link rel="stylesheet" media="only screen and (min-width: 600px) and (max-width: 1200px)" href="/theme/css/cv-medium.css">
|
||||||
|
<link rel="stylesheet" media="only screen and (min-width: 1200px)" href="/theme/css/cv-desktop.css">
|
||||||
|
<div id="post">
|
||||||
|
|
||||||
|
<div id="contact-info" class="vcard">
|
||||||
|
|
||||||
|
<!-- Microformats! -->
|
||||||
|
|
||||||
|
<h1 class="fn">J. Fernando Sánchez</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Email: <a class="email" href="mailto:admin@balkian.com">admin@balkian.com</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="objective">
|
||||||
|
<p>
|
||||||
|
I am a curious young engineer who happens to enjoy IT both as a career and as a hobby.
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clear"></div>
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<dd class="clear"></dd>
|
||||||
|
|
||||||
|
<dt>Education</dt>
|
||||||
|
<!--<dd>-->
|
||||||
|
<!--<h2>Postgraduate Researcher (PhD) - Technical University of Madrid (UPM) <span>2012-Present</span></h2>-->
|
||||||
|
<!--</dd>-->
|
||||||
|
<dd>
|
||||||
|
<h2>Telecommunications Engineering <span> <a href="http://www.etsit.upm.es">Technical University of Madrid (UPM)</a> - 2007-2012</span></h2>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dd class="clear"></dd>
|
||||||
|
|
||||||
|
<dt>Skills</dt>
|
||||||
|
<dd>
|
||||||
|
<h2>Programming Languages</h2>
|
||||||
|
<p>Used frequently: Python, Javascript/CoffeeScript, Bash/Shell, Java and Ruby</p>
|
||||||
|
<p>Also programmed in: PHP, C, C++, Objective C and Haskell</p>
|
||||||
|
|
||||||
|
<h2>Frameworks and libraries</h2>
|
||||||
|
<p>Node.js, Django, Ruby on Rails, QT, GTK2, JASON, RDFLib, Weka</p>
|
||||||
|
|
||||||
|
<h2>Development tools</h2>
|
||||||
|
<p>Git, Eclipse, Netbeans, Android SDK</p>
|
||||||
|
|
||||||
|
<h2>Others</h2>
|
||||||
|
<p>Latex, XMPP, GIMP, Inkscape</p>
|
||||||
|
|
||||||
|
<h2>Social Skills</h2>
|
||||||
|
<p>Working with and leading international teams. Presentation and communication skills: I conducted several presentations to audiences of ~100 people.</p>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dd class="clear"></dd>
|
||||||
|
|
||||||
|
<dt>Experience</dt>
|
||||||
|
<dd>
|
||||||
|
<h2>Graduate Research Fellow<span><a href="http://gsi.dit.upm.es">Intelligent Systems Group (GSI)</a> (GSI) - 2008-2012</span></h2>
|
||||||
|
<ul>
|
||||||
|
<li>Worked with Agent and Semantic technologies</li>
|
||||||
|
<li>Conducted my master thesis: Design and Implementation of an Agent Architecture Based on Web Hooks</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>IT Coordinator<span><a href="http://eestec.net">EESTEC International</a> - 2012-2013</span></h2>
|
||||||
|
<ul>
|
||||||
|
<li>Coordinated the work of a small international IT Team</li>
|
||||||
|
<li>In charge of the administration of the IT infrastructure of EESTEC: Plone portal, Mailman mailing lists, etc.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Oversight Committee Member<span><a href="http://eestec.net">EESTEC International</a> - 2012-2013</span></h2>
|
||||||
|
<ul>
|
||||||
|
<li>Supervised the work of the International Board</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Vice Chairman for External Affairs <span><a href="http://eestec.net">EESTEC International</a> - 2012-2013</span></h2>
|
||||||
|
<ul>
|
||||||
|
<li>Established connections with other Student Associations</li>
|
||||||
|
<li>Helped found new Observers (Local Groups)</li>
|
||||||
|
<li>Carried out International Board duties</li>
|
||||||
|
</ul>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dd class="clear"></dd>
|
||||||
|
|
||||||
|
<!--<dt>Hobbies</dt>-->
|
||||||
|
<!--<dd>Music, Sports and Cinema</dd>-->
|
||||||
|
|
||||||
|
<!--<dd class="clear"></dd>-->
|
||||||
|
|
||||||
|
<dt>References</dt>
|
||||||
|
<dd>Available on request</dd>
|
||||||
|
|
||||||
|
<dd class="clear"></dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<div class="clear"></div>
|
||||||
|
</div>
|
@ -1,7 +1,5 @@
|
|||||||
---
|
title: Projects
|
||||||
layout: default
|
layout: default
|
||||||
title: "Projects"
|
|
||||||
---
|
|
||||||
|
|
||||||
Ongoing Projects
|
Ongoing Projects
|
||||||
================
|
================
|
@ -1,7 +1,5 @@
|
|||||||
---
|
title: To-Do
|
||||||
layout: default
|
|
||||||
title: "To-Do"
|
|
||||||
---
|
|
||||||
<h2>Things To Do</h2>
|
<h2>Things To Do</h2>
|
||||||
This is intended as my public "todo.txt". Both to keep it accessible and to feel ashamed of the many things I leave undone:
|
This is intended as my public "todo.txt". Both to keep it accessible and to feel ashamed of the many things I leave undone:
|
||||||
<ul class="todo">
|
<ul class="todo">
|
109
cv/index.html
@ -1,109 +0,0 @@
|
|||||||
---
|
|
||||||
layout: default
|
|
||||||
title: "CV"
|
|
||||||
date: 2013-07-03 14:14:22
|
|
||||||
categories: cv resume
|
|
||||||
---
|
|
||||||
<link rel="stylesheet" media="only screen and (min-width: 0px) and (max-width: 599)" href="/css/cv-xs.css">
|
|
||||||
<link rel="stylesheet" media="only screen and (min-width: 600px) and (max-width: 1200px)" href="/css/cv-medium.css">
|
|
||||||
<link rel="stylesheet" media="only screen and (min-width: 1200px)" href="/css/cv-desktop.css">
|
|
||||||
<div id="post">
|
|
||||||
|
|
||||||
<!--<img src="images/cthulu.png" alt="Photo of Cthulu" id="pic" />-->
|
|
||||||
|
|
||||||
<div id="contact-info" class="vcard">
|
|
||||||
|
|
||||||
<!-- Microformats! -->
|
|
||||||
|
|
||||||
<h1 class="fn">J. Fernando Sánchez</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Email: <a class="email" href="mailto:admin@balkian.com">admin@balkian.com</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="objective">
|
|
||||||
<p>
|
|
||||||
I am a curious young engineer who happens to enjoy IT both as a career and as a hobby.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="clear"></div>
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dd class="clear"></dd>
|
|
||||||
|
|
||||||
<dt>Education</dt>
|
|
||||||
<!--<dd>-->
|
|
||||||
<!--<h2>Postgraduate Researcher (PhD) - Technical University of Madrid (UPM) <span>2012-Present</span></h2>-->
|
|
||||||
<!--</dd>-->
|
|
||||||
<dd>
|
|
||||||
<h2>Telecommunications Engineering <span> <a href="http://www.etsit.upm.es">Technical University of Madrid (UPM)</a> - 2007-2012</span></h2>
|
|
||||||
</dd>
|
|
||||||
|
|
||||||
<dd class="clear"></dd>
|
|
||||||
|
|
||||||
<dt>Skills</dt>
|
|
||||||
<dd>
|
|
||||||
<h2>Programming Languages</h2>
|
|
||||||
<p>Used frequently: Python, Javascript/CoffeeScript, Bash/Shell, Java and Ruby</p>
|
|
||||||
<p>Also programmed in: PHP, C, C++, Objective C and Haskell</p>
|
|
||||||
|
|
||||||
<h2>Frameworks and libraries</h2>
|
|
||||||
<p>Node.js, Django, Ruby on Rails, QT, GTK2, JASON, RDFLib, Weka</p>
|
|
||||||
|
|
||||||
<h2>Development tools</h2>
|
|
||||||
<p>Git, Eclipse, Netbeans, Android SDK</p>
|
|
||||||
|
|
||||||
<h2>Others</h2>
|
|
||||||
<p>Latex, XMPP, GIMP, Inkscape</p>
|
|
||||||
|
|
||||||
<h2>Social Skills</h2>
|
|
||||||
<p>Working with and leading international teams. Presentation and communication skills: I conducted several presentations to audiences of ~100 people.</p>
|
|
||||||
</dd>
|
|
||||||
|
|
||||||
<dd class="clear"></dd>
|
|
||||||
|
|
||||||
<dt>Experience</dt>
|
|
||||||
<dd>
|
|
||||||
<h2>Graduate Research Fellow<span><a href="http://gsi.dit.upm.es">Intelligent Systems Group (GSI)</a> (GSI) - 2008-2012</span></h2>
|
|
||||||
<ul>
|
|
||||||
<li>Worked with Agent and Semantic technologies</li>
|
|
||||||
<li>Conducted my master thesis: Design and Implementation of an Agent Architecture Based on Web Hooks</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>IT Coordinator<span><a href="http://eestec.net">EESTEC International</a> - 2012-2013</span></h2>
|
|
||||||
<ul>
|
|
||||||
<li>Coordinated the work of a small international IT Team</li>
|
|
||||||
<li>In charge of the administration of the IT infrastructure of EESTEC: Plone portal, Mailman mailing lists, etc.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Oversight Committee Member<span><a href="http://eestec.net">EESTEC International</a> - 2012-2013</span></h2>
|
|
||||||
<ul>
|
|
||||||
<li>Supervised the work of the International Board</li>
|
|
||||||
</ul>
|
|
||||||
<h2>Vice Chairman for External Affairs <span><a href="http://eestec.net">EESTEC International</a> - 2012-2013</span></h2>
|
|
||||||
<ul>
|
|
||||||
<li>Established connections with other Student Associations</li>
|
|
||||||
<li>Helped found new Observers (Local Groups)</li>
|
|
||||||
<li>Carried out International Board duties</li>
|
|
||||||
</ul>
|
|
||||||
</dd>
|
|
||||||
|
|
||||||
<dd class="clear"></dd>
|
|
||||||
|
|
||||||
<!--<dt>Hobbies</dt>-->
|
|
||||||
<!--<dd>Music, Sports and Cinema</dd>-->
|
|
||||||
|
|
||||||
<!--<dd class="clear"></dd>-->
|
|
||||||
|
|
||||||
<dt>References</dt>
|
|
||||||
<dd>Available on request</dd>
|
|
||||||
|
|
||||||
<dd class="clear"></dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<div class="clear"></div>
|
|
||||||
|
|
||||||
</div>
|
|
103
develop_server.sh
Executable file
@ -0,0 +1,103 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
##
|
||||||
|
# This section should match your Makefile
|
||||||
|
##
|
||||||
|
PY=${PY:-python}
|
||||||
|
PELICAN=${PELICAN:-pelican}
|
||||||
|
PELICANOPTS=
|
||||||
|
|
||||||
|
BASEDIR=$(pwd)
|
||||||
|
INPUTDIR=$BASEDIR/content
|
||||||
|
OUTPUTDIR=$BASEDIR/output
|
||||||
|
CONFFILE=$BASEDIR/pelicanconf.py
|
||||||
|
|
||||||
|
###
|
||||||
|
# Don't change stuff below here unless you are sure
|
||||||
|
###
|
||||||
|
|
||||||
|
SRV_PID=$BASEDIR/srv.pid
|
||||||
|
PELICAN_PID=$BASEDIR/pelican.pid
|
||||||
|
|
||||||
|
function usage(){
|
||||||
|
echo "usage: $0 (stop) (start) (restart) [port]"
|
||||||
|
echo "This starts Pelican in debug and reload mode and then launches"
|
||||||
|
echo "an HTTP server to help site development. It doesn't read"
|
||||||
|
echo "your Pelican settings, so if you edit any paths in your Makefile"
|
||||||
|
echo "you will need to edit your settings as well."
|
||||||
|
exit 3
|
||||||
|
}
|
||||||
|
|
||||||
|
function alive() {
|
||||||
|
kill -0 $1 >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
function shut_down(){
|
||||||
|
PID=$(cat $SRV_PID)
|
||||||
|
if [[ $? -eq 0 ]]; then
|
||||||
|
if alive $PID; then
|
||||||
|
echo "Stopping HTTP server"
|
||||||
|
kill $PID
|
||||||
|
else
|
||||||
|
echo "Stale PID, deleting"
|
||||||
|
fi
|
||||||
|
rm $SRV_PID
|
||||||
|
else
|
||||||
|
echo "HTTP server PIDFile not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
PID=$(cat $PELICAN_PID)
|
||||||
|
if [[ $? -eq 0 ]]; then
|
||||||
|
if alive $PID; then
|
||||||
|
echo "Killing Pelican"
|
||||||
|
kill $PID
|
||||||
|
else
|
||||||
|
echo "Stale PID, deleting"
|
||||||
|
fi
|
||||||
|
rm $PELICAN_PID
|
||||||
|
else
|
||||||
|
echo "Pelican PIDFile not found"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function start_up(){
|
||||||
|
local port=$1
|
||||||
|
echo "Starting up Pelican and HTTP server"
|
||||||
|
shift
|
||||||
|
$PELICAN --debug --autoreload -r $INPUTDIR -o $OUTPUTDIR -s $CONFFILE $PELICANOPTS &
|
||||||
|
pelican_pid=$!
|
||||||
|
echo $pelican_pid > $PELICAN_PID
|
||||||
|
cd $OUTPUTDIR
|
||||||
|
$PY -m pelican.server $port &
|
||||||
|
srv_pid=$!
|
||||||
|
echo $srv_pid > $SRV_PID
|
||||||
|
cd $BASEDIR
|
||||||
|
sleep 1
|
||||||
|
if ! alive $pelican_pid ; then
|
||||||
|
echo "Pelican didn't start. Is the Pelican package installed?"
|
||||||
|
return 1
|
||||||
|
elif ! alive $srv_pid ; then
|
||||||
|
echo "The HTTP server didn't start. Is there another service using port" $port "?"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo 'Pelican and HTTP server processes now running in background.'
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
# MAIN
|
||||||
|
###
|
||||||
|
[[ ($# -eq 0) || ($# -gt 2) ]] && usage
|
||||||
|
port=''
|
||||||
|
[[ $# -eq 2 ]] && port=$2
|
||||||
|
|
||||||
|
if [[ $1 == "stop" ]]; then
|
||||||
|
shut_down
|
||||||
|
elif [[ $1 == "restart" ]]; then
|
||||||
|
shut_down
|
||||||
|
start_up $port
|
||||||
|
elif [[ $1 == "start" ]]; then
|
||||||
|
if ! start_up $port; then
|
||||||
|
shut_down
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
usage
|
||||||
|
fi
|
73
fabfile.py
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
from fabric.api import *
|
||||||
|
import fabric.contrib.project as project
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import SimpleHTTPServer
|
||||||
|
import SocketServer
|
||||||
|
|
||||||
|
# Local path configuration (can be absolute or relative to fabfile)
|
||||||
|
env.deploy_path = 'output'
|
||||||
|
DEPLOY_PATH = env.deploy_path
|
||||||
|
|
||||||
|
# Remote server configuration
|
||||||
|
production = 'root@localhost:22'
|
||||||
|
dest_path = '/var/www'
|
||||||
|
|
||||||
|
# Rackspace Cloud Files configuration settings
|
||||||
|
env.cloudfiles_username = 'my_rackspace_username'
|
||||||
|
env.cloudfiles_api_key = 'my_rackspace_api_key'
|
||||||
|
env.cloudfiles_container = 'my_cloudfiles_container'
|
||||||
|
|
||||||
|
|
||||||
|
def clean():
|
||||||
|
if os.path.isdir(DEPLOY_PATH):
|
||||||
|
local('rm -rf {deploy_path}'.format(**env))
|
||||||
|
local('mkdir {deploy_path}'.format(**env))
|
||||||
|
|
||||||
|
def build():
|
||||||
|
local('pelican -s pelicanconf.py')
|
||||||
|
|
||||||
|
def rebuild():
|
||||||
|
clean()
|
||||||
|
build()
|
||||||
|
|
||||||
|
def regenerate():
|
||||||
|
local('pelican -r -s pelicanconf.py')
|
||||||
|
|
||||||
|
def serve():
|
||||||
|
os.chdir(env.deploy_path)
|
||||||
|
|
||||||
|
PORT = 8000
|
||||||
|
class AddressReuseTCPServer(SocketServer.TCPServer):
|
||||||
|
allow_reuse_address = True
|
||||||
|
|
||||||
|
server = AddressReuseTCPServer(('', PORT), SimpleHTTPServer.SimpleHTTPRequestHandler)
|
||||||
|
|
||||||
|
sys.stderr.write('Serving on port {0} ...\n'.format(PORT))
|
||||||
|
server.serve_forever()
|
||||||
|
|
||||||
|
def reserve():
|
||||||
|
build()
|
||||||
|
serve()
|
||||||
|
|
||||||
|
def preview():
|
||||||
|
local('pelican -s publishconf.py')
|
||||||
|
|
||||||
|
def cf_upload():
|
||||||
|
rebuild()
|
||||||
|
local('cd {deploy_path} && '
|
||||||
|
'swift -v -A https://auth.api.rackspacecloud.com/v1.0 '
|
||||||
|
'-U {cloudfiles_username} '
|
||||||
|
'-K {cloudfiles_api_key} '
|
||||||
|
'upload -c {cloudfiles_container} .'.format(**env))
|
||||||
|
|
||||||
|
@hosts(production)
|
||||||
|
def publish():
|
||||||
|
local('pelican -s publishconf.py')
|
||||||
|
project.rsync_project(
|
||||||
|
remote_dir=dest_path,
|
||||||
|
exclude=".DS_Store",
|
||||||
|
local_dir=DEPLOY_PATH.rstrip('/') + '/',
|
||||||
|
delete=True,
|
||||||
|
extra_opts='-c',
|
||||||
|
)
|
52
index.html
@ -1,52 +0,0 @@
|
|||||||
---
|
|
||||||
layout: default
|
|
||||||
title: Blog
|
|
||||||
categories: blog
|
|
||||||
---
|
|
||||||
<!-- Pagination links -->
|
|
||||||
{% if paginator.total_pages > 0 %}
|
|
||||||
<div class="pagination pag-top">
|
|
||||||
{% if paginator.previous_page %}
|
|
||||||
<span class="previouspage"><i class="icon-chevron-sign-left"></i><a href="/{% if paginator.previous_page > 2 %}page{{ paginator.previous_page }}{% endif %}"> Newer Posts</a></span>
|
|
||||||
{% else %}
|
|
||||||
<span class="previouspage" style="visibility:hidden;"><i class="icon-chevron-sign-left"></i> Newer Posts</span>
|
|
||||||
{% endif %}
|
|
||||||
<span class="page_number ">Page {{ paginator.page }} of {{ paginator.total_pages }}</span>
|
|
||||||
{% if paginator.next_page %}
|
|
||||||
<span class="nextpage"><a href="/page{{ paginator.next_page }}"> Older Posts </a> <i class="icon-chevron-sign-right"></i></span>
|
|
||||||
{% else %}
|
|
||||||
<span class="nextpage" style="visibility:hidden;">Older Posts <i class="icon-chevron-sign-right"></i></span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- This loops through the paginated posts -->
|
|
||||||
{% for post in paginator.posts %}
|
|
||||||
<div class="posthead">
|
|
||||||
<h2><a href="{{ post.url }}" class="title">{{ post.title }}</a></h2>
|
|
||||||
<span class="date">{{ post.date | date_to_string }}</span>
|
|
||||||
{% for c in post.tags %}
|
|
||||||
<span class="label label-success">{{ c }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<div class="excerpt">
|
|
||||||
{{ post.excerpt }}
|
|
||||||
</div>
|
|
||||||
<span><a href="{{ post.url }}"><i class="icon-pl2s"></i> Read more...</a></span>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if paginator.total_pages > 0 %}
|
|
||||||
<div class="pagination pag-bottom">
|
|
||||||
{% if paginator.previous_page %}
|
|
||||||
<span class="previouspage"><i class="icon-chevron-sign-left"></i><a href="/{% if paginator.previous_page > 2 %}page{{ paginator.previous_page }}{% endif %}"> Newer Posts</a></span>
|
|
||||||
{% else %}
|
|
||||||
<span class="previouspage" style="display:none;"><i class="icon-chevron-sign-left"></i> Newer Posts</span>
|
|
||||||
{% endif %}
|
|
||||||
<span class="page_number ">Page {{ paginator.page }} of {{ paginator.total_pages }}</span>
|
|
||||||
{% if paginator.next_page %}
|
|
||||||
<span class="nextpage"><a href="/page{{ paginator.next_page }}"> Older Posts </a> <i class="icon-chevron-sign-right"></i></span>
|
|
||||||
{% else %}
|
|
||||||
<span class="nextpage" style="display:none;">Older Posts <i class="icon-chevron-sign-right"></i></span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
56
pelicanconf.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*- #
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
AUTHOR = u'J. Fernando S\xe1nchez'
|
||||||
|
SITENAME = u'balkian.com'
|
||||||
|
SITEURL = ''
|
||||||
|
|
||||||
|
PATH = 'content'
|
||||||
|
|
||||||
|
TIMEZONE = 'Europe/Paris'
|
||||||
|
|
||||||
|
DEFAULT_LANG = u'en'
|
||||||
|
|
||||||
|
# Feed generation is usually not desired when developing
|
||||||
|
FEED_ALL_ATOM = None
|
||||||
|
CATEGORY_FEED_ATOM = None
|
||||||
|
TRANSLATION_FEED_ATOM = None
|
||||||
|
AUTHOR_FEED_ATOM = None
|
||||||
|
AUTHOR_FEED_RSS = None
|
||||||
|
|
||||||
|
# Blogroll
|
||||||
|
LINKS = (('Pelican', 'http://getpelican.com/'),
|
||||||
|
('Python.org', 'http://python.org/'),
|
||||||
|
('Jinja2', 'http://jinja.pocoo.org/'),
|
||||||
|
('You can modify those links in your config file', '#'),)
|
||||||
|
|
||||||
|
# Social widget
|
||||||
|
SOCIAL = (('You can add links in your config file', '#'),
|
||||||
|
('Another social link', '#'),)
|
||||||
|
|
||||||
|
DEFAULT_PAGINATION = 10
|
||||||
|
|
||||||
|
# Uncomment following line if you want document-relative URLs when developing
|
||||||
|
#RELATIVE_URLS = True
|
||||||
|
THEME = "balkiantheme"
|
||||||
|
|
||||||
|
def date_to_string(date):
|
||||||
|
return date.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
JINJA_FILTERS = {'date_to_string': date_to_string}
|
||||||
|
|
||||||
|
PLUGIN_PATH = ["plugins/",]
|
||||||
|
PLUGINS = ["neighbors",]
|
||||||
|
|
||||||
|
PYGMENTS_RST_OPTIONS = {'linenos': 'table'}
|
||||||
|
|
||||||
|
STATIC_PATHS = [
|
||||||
|
'extra/CNAME',
|
||||||
|
'extra/favicon',
|
||||||
|
'fonts'
|
||||||
|
]
|
||||||
|
EXTRA_PATH_METADATA = {
|
||||||
|
'extra/CNAME': {'path': 'CNAME'},
|
||||||
|
'extra/favicon': {'path': 'favicon'},
|
||||||
|
}
|
59
plugins/neighbors.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Neighbor Articles Plugin for Pelican
|
||||||
|
====================================
|
||||||
|
|
||||||
|
This plugin adds ``next_article`` (newer) and ``prev_article`` (older)
|
||||||
|
variables to the article's context
|
||||||
|
"""
|
||||||
|
from pelican import signals
|
||||||
|
|
||||||
|
def iter3(seq):
|
||||||
|
it = iter(seq)
|
||||||
|
nxt = None
|
||||||
|
cur = next(it)
|
||||||
|
for prv in it:
|
||||||
|
yield nxt, cur, prv
|
||||||
|
nxt, cur = cur, prv
|
||||||
|
yield nxt, cur, None
|
||||||
|
|
||||||
|
def get_translation(article, prefered_language):
|
||||||
|
if not article:
|
||||||
|
return None
|
||||||
|
for translation in article.translations:
|
||||||
|
if translation.lang == prefered_language:
|
||||||
|
return translation
|
||||||
|
return article
|
||||||
|
|
||||||
|
def set_neighbors(articles, next_name, prev_name):
|
||||||
|
for nxt, cur, prv in iter3(articles):
|
||||||
|
exec("cur.{} = nxt".format(next_name))
|
||||||
|
exec("cur.{} = prv".format(prev_name))
|
||||||
|
|
||||||
|
for translation in cur.translations:
|
||||||
|
exec(
|
||||||
|
"translation.{} = get_translation(nxt, translation.lang)".format(
|
||||||
|
next_name))
|
||||||
|
exec(
|
||||||
|
"translation.{} = get_translation(prv, translation.lang)".format(
|
||||||
|
prev_name))
|
||||||
|
|
||||||
|
def neighbors(generator):
|
||||||
|
set_neighbors(generator.articles, 'next_article', 'prev_article')
|
||||||
|
|
||||||
|
for category, articles in generator.categories:
|
||||||
|
articles.sort(key=(lambda x: x.date), reverse=(True))
|
||||||
|
set_neighbors(
|
||||||
|
articles, 'next_article_in_category', 'prev_article_in_category')
|
||||||
|
|
||||||
|
if hasattr(generator, 'subcategories'):
|
||||||
|
for subcategory, articles in generator.subcategories:
|
||||||
|
articles.sort(key=(lambda x: x.date), reverse=(True))
|
||||||
|
index = subcategory.name.count('/')
|
||||||
|
next_name = 'next_article_in_subcategory{}'.format(index)
|
||||||
|
prev_name = 'prev_article_in_subcategory{}'.format(index)
|
||||||
|
set_neighbors(articles, next_name, prev_name)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
signals.article_generator_finalized.connect(neighbors)
|
24
publishconf.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*- #
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# This file is only used if you use `make publish` or
|
||||||
|
# explicitly specify it as your config file.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.append(os.curdir)
|
||||||
|
from pelicanconf import *
|
||||||
|
|
||||||
|
SITEURL = 'http://balkian.com'
|
||||||
|
RELATIVE_URLS = False
|
||||||
|
|
||||||
|
FEED_ALL_ATOM = 'feeds/all.atom.xml'
|
||||||
|
CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'
|
||||||
|
|
||||||
|
DELETE_OUTPUT_DIRECTORY = True
|
||||||
|
|
||||||
|
# Following items are often useful when publishing
|
||||||
|
|
||||||
|
#DISQUS_SITENAME = ""
|
||||||
|
#GOOGLE_ANALYTICS = ""
|