Convert the whole site to use Pelican instead of Jekyll
This commit is contained in:
25
content/blog/2011-05-12-ethical-hacking-2011.rst
Normal file
25
content/blog/2011-05-12-ethical-hacking-2011.rst
Normal file
@@ -0,0 +1,25 @@
|
||||
Ethical Hacking 2012
|
||||
####################
|
||||
|
||||
:date: 2011-05-12T20:54:42Z
|
||||
:category: blog
|
||||
:tags: conference
|
||||
:url: blog/2011/5/12/ethical-hacking-2011.html
|
||||
:save_as: blog/2011/5/12/ethical-hacking-2011.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Today I went to the Ethical Hacking conference with my boss. It was my first appearance at such
|
||||
conferences, but I hope there will be more. Although we just started to redesign our IT security
|
||||
infrastructure with a 90% clear goal, it was nice to hear that everything is vulnerable. I was
|
||||
thinking if we should sell all our IT equipments, fire all our colleagues (you know, to prevent
|
||||
social engineering), and move to the South Americas to herd llamas or sheep, so the only danger
|
||||
would be some lurking pumas or jaguars. Or I simply leave my old background image on my desktop,
|
||||
from the well-known game, which says: Trust is a weakness.
|
||||
|
||||
Anyways, the conference was really nice. We heard about the weaknesses of Android, Oracle, and
|
||||
even FireWire. They showed some demos about everything, exploited some free and commercial
|
||||
software with no problem at all. We have seen how much power the virtualisation admin has
|
||||
(although I think it can be prevented, but I’m not sure yet). However, in the end, we could see
|
||||
that the Cloud is secure (or at least it can be, in a few months or so), so I’m not totally
|
||||
pessimistic. See you next time at Hacktivity!
|
@@ -0,0 +1,75 @@
|
||||
Gentoo hardened desktop with GNOME 3 – Round one
|
||||
################################################
|
||||
|
||||
:date: 2011-05-12T20:32:41Z
|
||||
:url: blog/2011/5/12/gentoo-hardened-desktop-with-gnome-3-round-one.html
|
||||
:save_as: blog/2011/5/12/gentoo-hardened-desktop-with-gnome-3-round-one.html
|
||||
:category: blog
|
||||
:tags: gentoo,gnome3,selinux
|
||||
:author: Gergely Polonkai
|
||||
:status: published
|
||||
|
||||
After having some hard times with Ubuntu (upgrading from 10.10 to 11.04), I decided to switch back
|
||||
to my old friend, Gentoo. As I’m currently learning about Linux hardening, I decided to use the
|
||||
new SELinux profile, which supports the v2 reference policy.
|
||||
|
||||
Installation was pretty easy, using the `Gentoo x86 Handbook
|
||||
<http://www.gentoo.org/doc/hu/handbook/handbook-x86.xml>`_. This profile automatically turns on
|
||||
the ``USE=selinux`` flag (so does the old SELinux profile), but deprecated ``FEATURE=loadpolicy``
|
||||
(which is turned on by the profile, so portage will complain about it until you disable it in
|
||||
``/etc/make.conf``).
|
||||
|
||||
For the kernel, I chose ``hardened-sources-2.6.37-r7``. This seems to be recent enough for my
|
||||
security testing needs. I turned on both SELinux, PaX and grsecurity. So far, I have no problem
|
||||
with it, but I don’t have X installed yet, which will screw up things for sure.
|
||||
|
||||
After having those hard times with Ubuntu mentioned before, I decided not to install Grub2 yet, as
|
||||
it renders things unusable (eg. my Windows 7 installation, which I sometimes need at the office).
|
||||
So I installed Grub 0.97 (this is the only version marked as stable, as I remember), touched
|
||||
``/.autorelabel``, and reboot.
|
||||
|
||||
My first mistake was using an UUID as the root device on the kernel parameter list (I don’t want
|
||||
to list all the small mistakes like forgetting to include to correct SATA driver from my kernel
|
||||
and such). Maybe I was lame, but after including ``/dev/sda5`` instead of the UUID thing, it
|
||||
worked like…
|
||||
|
||||
Well, charm would not be the good word. For example, I forgot to install the ``lvm2`` package, so
|
||||
nothing was mounted except my root partition. After I installed it with the install CD, I assumed
|
||||
everything will be all right, but I was wrong.
|
||||
|
||||
``udev`` and LVM is a critical point in a hardened environment. ``udev`` itself doesn’t want to
|
||||
work without the ``CONFIG_DEVFS_TEMPFS=y`` kernel option, so I also had to change that. It seemed
|
||||
that it can be done without the install CD, as it compiled the kernel with no problems. However,
|
||||
when it reached the point when it compresses the kernel with gzip, it stopped with a ``Permission
|
||||
denied`` message (although it was running with root privileges).
|
||||
|
||||
The most beautiful thing in the hardened environment with Mandatory Access Control enabled is that
|
||||
root is not a real power user any more by default. You can get this kind of messages many times.
|
||||
There are many tools to debug these, I will talk about these later.
|
||||
|
||||
So, my ``gzip`` needed a fix. After digging a bit on the Internet, I found that the guilty thing
|
||||
is text relocation, which can be corrected if ``gzip`` is compiled with PIC enabled. Thus, I
|
||||
turned on ``USE=pic`` flag globally, and tried to remerge gzip. Of course it failed, as it had to
|
||||
use gzip to unpack the gzip sources. So it did when I tried to install the PaX tools and
|
||||
``gradm`` to turn these checks off. The install CD came to the rescue again, with which I
|
||||
successfully recompiled gzip, and with this new gzip, I compressed my new kernel, with which udev
|
||||
started successfully. So far, so good, let’s try to reboot!
|
||||
|
||||
Damn, LVM is still not working. So I decided to finally consult the Gentoo hardened guide. It
|
||||
says that the LVM startup scripts under ``/lib/rcscripts/…`` must be modified, so LVM will put its
|
||||
lock files under ``/etc/lvm/lock`` instead of ``/dev/.lvm``. After this step and a reboot, LVM
|
||||
worked fine (finally).
|
||||
|
||||
The next thing was the file system labelling. SELinux should automatically relabel the entire
|
||||
file system at boot time whenever it finds the ``/.autorelabel`` file. Well, in my case it didn’t
|
||||
happen. After checking the `Gentoo Hardening <http://wiki.gentoo.org/wiki/Hardened_Gentoo>`_
|
||||
docs, I realised that the ``rlpkg`` program does exactly the same (as far as I know, it is
|
||||
designed specifically for Gentoo). So I ran ``rlpkg``, and was kind of shocked. It says it will
|
||||
relabel ext2, ext3, xfs and JFS partitions. Oh great, no ext4 support? Well, consulting the
|
||||
forums and adding some extra lines to ``/etc/portage/package.keywords`` solved the problem
|
||||
(``rlpkg`` and some dependencies had to have the ``~x86`` keyword set). Thus, ``rlpkg``
|
||||
relabelled my file systems (I checked some directories with ``ls -lZ``, it seemed good for me).
|
||||
|
||||
Now it seems that everything is working fine, except the tons of audit messages. Tomorrow I will
|
||||
check them with ``audit2why`` or ``audit2allow`` to see if it is related with my SELinux lameness,
|
||||
or with a bug in the policy included with Gentoo.
|
32
content/blog/2011-05-13-zabbix-performance-tip.rst
Normal file
32
content/blog/2011-05-13-zabbix-performance-tip.rst
Normal file
@@ -0,0 +1,32 @@
|
||||
Zabbix performance tip
|
||||
######################
|
||||
|
||||
:date: 2011-05-13T19:03:31Z
|
||||
:category: blog
|
||||
:tags: zabbix,monitoring
|
||||
:url: blog/2011/5/13/zabbix-performance-tip.html
|
||||
:save_as: blog/2011/5/13/zabbix-performance-tip.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Recently I have switched from `MRTG <http://oss.oetiker.ch/mrtg/>`_ + `Cacti
|
||||
<http://www.cacti.net/>`_ + `Nagios <http://www.nagios.org/>`_ + `Gnokii
|
||||
<http://www.gnokii.org/>`_ to `Zabbix <http://www.zabbix.com/>`_, and I must say I’m more than
|
||||
satisfied with it. It can do anything the former tools did, and much more. First of all, it can
|
||||
do the same monitoring as Nagios did, but it does much more fine. It can check several parameters
|
||||
within one request, so network traffic is kept down. Also, its web front-end can generate any
|
||||
kinds of graphs from the collected data, which took Cacti away. Also, it can do SNMP queries
|
||||
(v1-v3), so querying my switches’ port states and traffic made easy, taking MRTG out of the
|
||||
picture (I know Cacti can do it either, it had historical reasons we had both tools installed).
|
||||
And the best part: it can send SMS messages via a GSM modem natively, while Nagios had to use
|
||||
Gnokii. The trade-off is, I had to install Zabbix agent on all my monitored machines, but I think
|
||||
it worths the price. I even have had to install NRPE to monitor some parameters, which can be a
|
||||
pain on Windows hosts, while Zabbix natively supports Windows, Linux and Mac OS/X.
|
||||
|
||||
So I only had to create a MySQL database (which I already had for NOD32 central management), and
|
||||
install Zabbix server. Everything went fine, until I reached about 1300 monitored parameters.
|
||||
MySQL seemed to be a bit slow on disk writes, so my Zabbix “queue” filled up in no time. After
|
||||
reading some forums, I decided to switch to PostgreSQL instead. Now it works like charm, even
|
||||
with the default Debian settings. However, I will have to add several more parameters, and my
|
||||
boss wants as many graphs as you can imagine, so I’m more than sure that I will have to fine tune
|
||||
my database later.
|
@@ -0,0 +1,26 @@
|
||||
Gentoo hardened desktop with GNOME 3 – Round two
|
||||
################################################
|
||||
|
||||
:date: 2011-05-18T10:28:14Z
|
||||
:category: blog
|
||||
:tags: gentoo,gnome3,selinux
|
||||
:url: blog/2011/5/18/gentoo-hardened-desktop-with-gnome-3-round-two.html
|
||||
:save_as: blog/2011/5/18/gentoo-hardened-desktop-with-gnome-3-round-two.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
After several hours of ``package.keywords``/``package.use`` editing and package compiling, I
|
||||
managed to install GNOME 3 on my notebook. Well, I mean, the GNOME 3 packages. Unfortunately the
|
||||
``fglrx`` driver didn’t seem to recognise my ATI Mobility M56P card, and the open source driver
|
||||
didn’t want to give me GLX support. When I finally found some clues on what should I do, I had to
|
||||
use my notebook for work, so I installed Fedora 14 on it. Then I realised that GNOME 3 is already
|
||||
included in Rawhide (Fedora 15), so I quickly downloaded and installed that instead. Now I have
|
||||
to keep this machine in a working state for a few days, so I will learn SELinux stuff in its
|
||||
native environment.
|
||||
|
||||
When I installed Fedora 14, the first AVC message popped up after about ten minutes. That was a
|
||||
good thing, as I wanted to see ``setroubleshoot`` in action. However, in Fedora 15, the AVC
|
||||
bubbles didn’t show up even after a day. I raised my left eyebrow and said that’s impossible,
|
||||
SELinux must be disabled. And it’s not! It’s even in enforcing mode! And it works just fine. I
|
||||
like it, and I hope I will be able to get the same results with Gentoo if I can get back to
|
||||
testing…
|
@@ -0,0 +1,34 @@
|
||||
Citrix XenServer 5.5 vs. Debian 5.0 upgrade to 6.0
|
||||
##################################################
|
||||
|
||||
:date: 2011-05-27T17:33:41Z
|
||||
:category: blog
|
||||
:tags: citrix-xenserver,debian
|
||||
:url: blog/2011/5/27/citrix-xenserver-vs-debian-5-0-upgrade-to-6-0.html
|
||||
:save_as: blog/2011/5/27/citrix-xenserver-vs-debian-5-0-upgrade-to-6-0.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Few weeks ago I’ve upgraded two of our Debian based application servers from 5.0 to 6.0.
|
||||
Everything went fine, as the upgraded packages worked well with the 4.2 JBoss instances. For the
|
||||
new kernel we needed a reboot, but as the network had to be rebuilt, I postponed this reboot until
|
||||
the network changes. With the network, everything went fine again, we successfully migrated our
|
||||
mail servers behind a firewall. Also the Xen server (5.5.0, upgrade to 5.6 still has to wait for
|
||||
a week or so) revolted well with some storage disks added. But the application servers remained
|
||||
silent…
|
||||
|
||||
After checking the console, I realised that they don’t have an active console. And when I tried
|
||||
to manually start them, XenServer refused with a message regarding ``pygrub``.
|
||||
|
||||
To understand the problem, I had to understand how XenServer boots Debian. It reads the
|
||||
``grub.conf`` on the first partition’s root or ``/boot`` directory, and starts the first option,
|
||||
without asking (correct me, if I’m mistaken somewhere). However, this ``pygrub`` thing can not
|
||||
parse the new, grub2 config. This is kinda frustrating.
|
||||
|
||||
For the first step, I quickly installed a new Debian 5.0 system from my template. Then I attached
|
||||
the disks of the faulty virtual machine, and mounted all its partitions. This way I could reach
|
||||
my faulty 6.0 system with a chroot shell, from which I could install the ``grub-legacy`` package
|
||||
instead of grub, install the necessary kernel and XenServer tools (which were missing from both
|
||||
machines somehow), then halt the rescue system, and start up the original instance.
|
||||
|
||||
Next week I will do an upgrade on the XenServer to 5.6.1. I hope no such problems will occur.
|
@@ -0,0 +1,23 @@
|
||||
Oracle Database “incompatible” with Oracle Linux?
|
||||
#################################################
|
||||
|
||||
:date: 2011-05-27T17:53:31Z
|
||||
:category: blog
|
||||
:tags: linux,oracle
|
||||
:url: blog/2011/5/27/oracle-database-incompatible-with-oracle-linux.html
|
||||
:save_as: blog/2011/5/27/oracle-database-incompatible-with-oracle-linux.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Today I gave a shot to install `Oracle Linux
|
||||
<http://www.oracle.com/us/technologies/linux/overview/index.html>`_. I thought I could easily
|
||||
install an Oracle DBA on it. Well, I was naive.
|
||||
|
||||
As only the 5.2 version is supported by XenServer 5.5, I downloaded that version of Oracle Linux.
|
||||
Installing it was surprisingly fast and easy, it asked almost nothing, and booted without any
|
||||
problems.
|
||||
|
||||
After this came the DBA, 10.2, which bloated an error message in my face saying that this is an
|
||||
unsupported version of Linux. Bah.
|
||||
|
||||
Is it only me, or is it really strange that Oracle doesn’t support their own distro?
|
@@ -0,0 +1,29 @@
|
||||
Proxy only non-existing files with mod_proxy and mod_rewrite
|
||||
############################################################
|
||||
|
||||
:date: 2011-06-10T14:20:43Z
|
||||
:category: blog
|
||||
:tags: apache
|
||||
:url: blog/2011/6/10/proxy-only-non-existing-files-with-mod-proxy-and-mod-rewrite.html
|
||||
:save_as: blog/2011/6/10/proxy-only-non-existing-files-with-mod-proxy-and-mod-rewrite.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Today I got an interesting task. I had to upload some pdf documents to a site. The domain is
|
||||
ours, but we don’t have access to the application server that is hosting the page yet. Until we
|
||||
get it in our hands, I did a trick.
|
||||
|
||||
I enabled `mod_rewrite`, `mod_proxy` and `mod_proxy_http`, then added the following lines to my
|
||||
apache config:
|
||||
|
||||
.. code-block:: apache
|
||||
|
||||
RewriteEngine on
|
||||
RewriteRule ^/$ http://172.16.72.131:8080/ [QSA,L,P]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^/(.*) http://172.16.72.131:8080/$1 [QSA,L,P]
|
||||
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
|
||||
I’m not totally sure it’s actually secure, but it works for now.
|
26
content/blog/2011-09-18-inverse-of-sort.rst
Normal file
26
content/blog/2011-09-18-inverse-of-sort.rst
Normal file
@@ -0,0 +1,26 @@
|
||||
Inverse of `sort`
|
||||
#################
|
||||
|
||||
:date: 2011-09-18T14:57:31Z
|
||||
:category: blog
|
||||
:tags: linux,command-line
|
||||
:url: blog/2011/9/18/inverse-of-sort.html
|
||||
:save_as: blog/2011/9/18/inverse-of-sort.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I’m using \*NIX systems for about 14 years now, but it can still show me new things. Today I had
|
||||
to generate a bunch of random names. I’ve create a small perl script which generates permutations
|
||||
of some usual Hungarian first and last names, occasionally prefixing it with a ‘Dr.’ title or
|
||||
using double first names. For some reasons I forgot to include uniqueness check in the script.
|
||||
When I ran it in the command line, I realized the mistake, so I appended ``| sort | uniq`` to the
|
||||
command line. So I had around 200 unique names, but in alphabetical order, which was awful for my
|
||||
final goal. Thus, I tried shell commands like ``rand`` to create a random order, and when many of
|
||||
my tries failed, the idea popped in my mind (not being a native English speaker): “I don’t have to
|
||||
create »random order«, but »shuffle the list«. So I started typing ``shu``, pressed Tab in the
|
||||
Bash shell, and voilà! ``shuf`` is the winner, it does just exactly what I need:
|
||||
|
||||
**NAME**
|
||||
shuf - generate random permutations
|
||||
|
||||
Thank you, Linux Core Utils! :)
|
@@ -0,0 +1,14 @@
|
||||
Why you should always test your software with production data
|
||||
#############################################################
|
||||
|
||||
:date: 2011-12-11T12:14:51Z
|
||||
:category: blog
|
||||
:tags: development,testing,ranting
|
||||
:url: blog/2011/12/11/why-you-should-always-test-your-software-with-production-data.html
|
||||
:save_as: blog/2011/12/11/why-you-should-always-test-your-software-with-production-data.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I’m writing a software for my company in PHP, using the Symfony 2 framework. I’ve finished all
|
||||
the work, created some sample data, it loaded perfectly. Now I put the whole thing into
|
||||
production and tried to upload the production data into it. Guess what… it didn’t load.
|
25
content/blog/2012-03-20-php-5-4-released.rst
Normal file
25
content/blog/2012-03-20-php-5-4-released.rst
Normal file
@@ -0,0 +1,25 @@
|
||||
PHP 5.4 released
|
||||
################
|
||||
|
||||
:date: 2012-03-20T13:31:12Z
|
||||
:category: blog
|
||||
:tags: php
|
||||
:url: blog/2012/3/20/php-5-4-released.html
|
||||
:save_as: blog/2012/3/20/php-5-4-released.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
After a long time of waiting, PHP announced 5.4 release on 1 March (also, today they announced
|
||||
that they finally migrate to Git, which is sweet from my point of view, but it doesn’t really
|
||||
matter).
|
||||
|
||||
About a year ago we became very agressive towards a developer who created our internal e-learning
|
||||
system. Their database was very insecure, and they didn’t really follow industry standards in
|
||||
many ways. Thus, we forced them to move from Windows + Apache 2.0 + PHP 5.2 + MySQL 4.0 to Debian
|
||||
Linux 6.0 + Apache 2.2 + PHP 5.3 + MySQL 5.1. It was fun (well, from our point of view), as their
|
||||
coders… well… they are not so good. The code that ran “smoothly” on the old system failed at many
|
||||
points on the new one. So they code and code, and write more code. And they still didn’t finish.
|
||||
And now 5.4 is here. Okay, I know it will take some time to get into the Debian repositories, but
|
||||
it’s here. And they removed ``register_globals``, which will kill that funny code again at so
|
||||
many points that they will soon get to rewrite the whole code to make it work. And I just sit
|
||||
here in my so-much-comfortable chair, and laugh. Am I evil?
|
29
content/blog/2012-03-27-fast-world-fast-updates.rst
Normal file
29
content/blog/2012-03-27-fast-world-fast-updates.rst
Normal file
@@ -0,0 +1,29 @@
|
||||
Fast world, fast updates
|
||||
########################
|
||||
|
||||
:date: 2012-03-27T06:18:43Z
|
||||
:category: blog
|
||||
:tags: linux
|
||||
:url: blog/2012/3/27/fast-world-fast-updates.html
|
||||
:save_as: blog/2012/3/27/fast-world-fast-updates.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
We live in a fast world, that’s for sure. When I first heard about Ubuntu Linux and their goals,
|
||||
I was happy: they gave a Debian to everyone, but in different clothes. It had fresh software in
|
||||
it, and even they gave support of a kind. It was easy to install and use, even if one had no
|
||||
Linux experience before. So people liked it. I’ve even installed it on some of my servers
|
||||
because of the new package versions that came more often. Thus I got an up to date system.
|
||||
However, it had a price. After a while, security updates came more and more often, and when I had
|
||||
a new critical update every two or three days, I’ve decided to move back to Debian. Fortunately I
|
||||
did this at the time of a new release, so I didn’t really loose any features.
|
||||
|
||||
After a few years passed, even Debian is heading this very same way. But as I see, the cause is
|
||||
not the same. It seems that upstream software is hitting these bugs, and even the Debian guys
|
||||
don’t have the time to check for them. At the time of a GNOME version bump (yes, GNOME 3 is a
|
||||
really big one for the UN\*X-like OSes), when hundreds of packages need to be checked, security
|
||||
bugs show off more often. On the other hand however, Debian is releasing a new security update
|
||||
every day (I had one on each of the last three days). This, of course, is good from one point of
|
||||
view as we get a system that is more secure, but most administrators don’t have maintenance
|
||||
windows this often. I can think of some alternatives like Fedora, but do I really have to change?
|
||||
Dear fellow developers, please code more carefully instead!
|
23
content/blog/2012-06-14-wordpress-madness.rst
Normal file
23
content/blog/2012-06-14-wordpress-madness.rst
Normal file
@@ -0,0 +1,23 @@
|
||||
Wordpress madness
|
||||
#################
|
||||
|
||||
:date: 2012-06-14T06:40:12Z
|
||||
:category: blog
|
||||
:tags: wordpress,ranting
|
||||
:url: blog/2012/6/14/wordpress-madness.html
|
||||
:save_as: blog/2012/6/14/wordpress-madness.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I’m a bit fed up that I had to install `MySQL <http://www.mysql.com/>`_ on my server to have
|
||||
`Wordpress <http://wordpress.org/>`_ working, so I’ve Googled a bit to find a solution for my
|
||||
pain. I found `this <http://codex.wordpress.org/Using_Alternative_Databases>`_. I don’t know
|
||||
when this post was written, but I think it’s a bit out of date. I mean come on, PDO is the part
|
||||
of PHP for ages now, and they say adding a DBAL to the dependencies would be a project as large as
|
||||
(or larger than) WP itself. Well, yes, but PHP is already a dependency, isn’t it? Remove it
|
||||
guys, it’s too large!
|
||||
|
||||
Okay, to be serious… Having a heavily MySQL dependent codebase is a bad thing in my opinion, and
|
||||
changing it is no easy task. But once it is done, it would be a child’s play to keep it up to
|
||||
date, and to port WP to other database backends. And it would be more than enough to call it 4.0,
|
||||
and raising version numbers fast is a must nowadays (right, Firefox and Linux Kernel guys?)
|
@@ -0,0 +1,25 @@
|
||||
SSH login FAILed on Red Had Enterprise Linux 6.2
|
||||
################################################
|
||||
|
||||
:date: 2012-06-18T18:28:45Z
|
||||
:category: blog
|
||||
:tags: linux,selinux,ssh,red-hat
|
||||
:url: blog/2012/6/18/ssh-login-failed-on-red-hat-enterprise-linux-6-2.html
|
||||
:save_as: blog/2012/6/18/ssh-login-failed-on-red-hat-enterprise-linux-6-2.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Now this was a mistake I should not have done…
|
||||
|
||||
About a month ago I have moved my AWS EC2 machine from Amazon Linux to RHEL 6.2. This was good.
|
||||
I have moved all my files and stuff, recreated my own user, everything was just fine. Then I
|
||||
copied my `gitosis <https://github.com/tv42/gitosis>`_ account (user ``git`` and its home
|
||||
directory). Then I tried to log in. It failed. I was blaming OpenSSH for a week or so, changed
|
||||
the config file in several ways, tried to change the permissions on ``~git/.ssh/*``, but still
|
||||
nothing. Permission were denied, I was unable to push any of my development changes. Now after a
|
||||
long time of trying, I coincidently ``tail -f``-ed ``/var/log/audit/audit.log`` (wanted to open
|
||||
``auth.log`` instead) and that was my first good point. It told me that ``sshd`` was unable to
|
||||
read ``~git/.ssh/authorized_keys``, which gave me the idea to run ``restorecon`` on ``/home/git``.
|
||||
It solved the problem.
|
||||
|
||||
All hail SELinux and RBAC!
|
@@ -0,0 +1,31 @@
|
||||
Upgrades requiring a reboot on Linux? At last!
|
||||
##############################################
|
||||
|
||||
:date: 2012-06-22T20:04:51Z
|
||||
:category: blog
|
||||
:tags: linux
|
||||
:url: blog/2012/6/22/upgrades-requiring-a-reboot-on-linux-at-last.html
|
||||
:save_as: blog/2012/6/22/upgrades-requiring-a-reboot-on-linux-at-last.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I’ve recently received an article on Google+ about Fedora’s new idea: package upgrades that
|
||||
require a reboot. The article said that Linux guys have lost their primary adoo: “Haha! I don’t
|
||||
have to reboot my system to install system upgrades!” My answer was always this: “Well, actually
|
||||
you should…”
|
||||
|
||||
I think this can be a great idea if distros implement it well. PackageKit was a good first step
|
||||
on this road. That software could easily solve such an issue. However, it is sooo easy to do it
|
||||
wrong. The kernel, of course, can not be upgraded online (or could it be? I have some theories on
|
||||
this subject, wonder if it can be implemented…), but other packages are much different. From the
|
||||
users’ point of view the best would be if the packages would be upgraded in the background
|
||||
seemlessly. E.g. PackageKit should check if the given executable is running. If not, it should
|
||||
upgrade it, while notifying the user like “Hey dude, don’t start Anjuta now, I’m upgrading it!”,
|
||||
or simply denying to start it. Libraries are a bit different, as PackageKit should check if any
|
||||
running executables are using the library. Meanwhile, PK should also keep a notification
|
||||
somewhere telling the users that some packages could be upgraded, but without stopping
|
||||
this-and-that, it can not be done.
|
||||
|
||||
I know these things are easier said than done. But I think (a) users should tell such ideas to
|
||||
the developers and (b) developers (mostly large companies, like Microsoft or Apple) should listen
|
||||
to them, and at least think of these ideas. Some users are not as stupid as they think…
|
@@ -0,0 +1,67 @@
|
||||
Some thoughts about that dead Linux Desktop
|
||||
###########################################
|
||||
|
||||
:date: 2012-09-05T09:01:31Z
|
||||
:category: blog
|
||||
:tags: linux,ranting
|
||||
:url: blog/2012/9/5/some-thoughts-about-that-dead-linux-desktop.html
|
||||
:save_as: blog/2012/9/5/some-thoughts-about-that-dead-linux-desktop.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
There were some arguments in the near past on `What Killed the Linux Desktop
|
||||
<http://tirania.org/blog/archive/2012/Aug-29.html>`_. After reading many replies, like `Linus
|
||||
Torvalds’s
|
||||
<http://www.zdnet.com/linus-torvalds-on-the-linux-desktops-popularity-problems-7000003641/>`_, I
|
||||
have my own thoughts, too.
|
||||
|
||||
I know my place in the world, especially in the online community. I’m a Linux user for about 15
|
||||
years and a Linux administrator for 10 years now, beginning with WindowMaker and something that I
|
||||
remember as GNOME without a version number. I have committed some minor code chunks and
|
||||
translations in some minor projects, so I’m not really into it from the “write” side (well, until
|
||||
now, since I have began to write this blog, and much more, but don’t give a penny for my words
|
||||
until you see it).
|
||||
|
||||
I’m using Linux since 2.2 and GNOME since 1.whatever. It’s nice that a program compiled years ago
|
||||
still runs on today’s Linux kernel, especially if you see old DOS/Windows software failing to
|
||||
start on a new Windows 7 machine. I understand Linus’ point that breaking external APIs is bad,
|
||||
and I think it can work well on the kernel’s level. But the desktop level is much different. As
|
||||
the Linux Desktop has such competitors (like OS/X and Windows’ Aero and Metro), they have to give
|
||||
something new to the users almost every year to keep up with them. Eye candies are a must (yes,
|
||||
of course my techy fellows, they are worthless, but users *need* it), and they can not be created
|
||||
without extending APIs. And the old API… well, it fades away fast. I don’t really understand
|
||||
however, why they have to totally disappear, like `GTK_DIALOG_NO_SEPARATOR
|
||||
<http://developer.gnome.org/gtk/stable/GtkDialog.html#GtkDialogFlags>`_ in Gtk+3. It could be
|
||||
replaced with a 0 value (e.g: it won’t do anything). This way my old Gtk+2 program could compile
|
||||
with Gtk+3 nicely. Also, there could be a small software that goes through your source code and
|
||||
warn you about such deprecated (and no-doer but still working) things. Porting applications
|
||||
between Gtk+ (and thus, GNOME) versions became a real pain, which makes less enthusiast
|
||||
programmers stop developing for Linux. Since I’m a GNOME guy for years, I can tell nothing about
|
||||
Qt and KDE, but for the GNOME guys, this is a bad thing. As of alternatives, there is Java. No,
|
||||
wait… it turned out recently that `it has several security bugs
|
||||
<http://www.theregister.co.uk/2012/08/31/critical_flaw_found_in_patched_java>`_. Also it’s not
|
||||
that multiplatform as they say (I can’t find the article on that at the moment, but I have proof).
|
||||
Also, the JVMs out there eat up so much resources, which makes it a bit hard and expensive to use.
|
||||
|
||||
Also, I see another problem: those blasted package managers. RPM, DPKG, Portage, whatever. What
|
||||
the hell? Why are there so many? Why do developers reinvent the wheel? The nave is too small or
|
||||
there are to few spokes? Come on… we live in an open source world! Contribute to the one and
|
||||
only package manager (which one is that I don’t actually care)! I’m sure the two (three, many)
|
||||
bunches of develoeprs could make a deal. Thus, it could become better and “outsider” companies
|
||||
would be happier to distribute their software for Linux platforms.
|
||||
|
||||
And now that we get to the big companies. I don’t really understand them. nVidia and ATI made
|
||||
their own closed source drivers for Linux. Some other hardware vendors also write Linux drivers,
|
||||
and as the kernel API doesn’t really change, they will work for a long time. But what about
|
||||
desktop application vendors? Well, they try to stick to a desktop environment or two, and if they
|
||||
change too frequently, they stop developing for Linux, like Skype did (OK, maybe Skype has other
|
||||
reasons, but you see my point). But why? The main part for Linux programs is the Linux kernel
|
||||
and the basic userland like libc/stdlib++. If you write graphical software, it will have to use
|
||||
X-Windows. Yes, it’s much different in many ways, mostly because they have a… well… pretty ugly
|
||||
design by default. But still, it’s the same on every Linux distributions, as it became somewhat
|
||||
an industry standard, as it was already on the market back in the old UN\*X days. The protocol
|
||||
itself changed just like the Linux kernel: almost no change at all, just some new features.
|
||||
|
||||
So what kills the Linux desktop in my opinion is these constant wars inside, and the lack of
|
||||
support from the outside. Open Source is good, but until these (mostly the first) problems are
|
||||
not resolved, Linux Desktop can do nothing on the market. It’s a downward spiral hard to escape.
|
@@ -0,0 +1,73 @@
|
||||
How to start becoming a web developer
|
||||
#####################################
|
||||
|
||||
:date: 2012-09-07T18:12:12Z
|
||||
:category: blog
|
||||
:tags: development,technology
|
||||
:url: blog/2012/9/7/how-to-start-becoming-a-web-developer.html
|
||||
:save_as: blog/2012/9/7/how-to-start-becoming-a-web-developer.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
A friend of mine asked me today how to become a web developer. It took me a while, but I made up
|
||||
a checklist. It’s short, but it’s enough for the first steps.
|
||||
|
||||
First of all, learn English
|
||||
===========================
|
||||
|
||||
Well, if you read this, maybe this was a bad first point…
|
||||
|
||||
Choose a language and stick to it!
|
||||
==================================
|
||||
|
||||
For the UN\*X/Linux line, there is PHP. It’s free, easy to learn, and has many free tools and
|
||||
documentations available. It can be used in a functional or an object-oriented way.
|
||||
|
||||
C# is another good way to start, but for the Windows line. It’s fully object oriented, and the
|
||||
web is full of tutorials, how-tos and other resources.
|
||||
|
||||
Learn the basics of the system you are working on
|
||||
=================================================
|
||||
|
||||
To become a good developer, learn at least the basics of the system you are working on. Basic
|
||||
commands can always come in handy. Debugging (yes, you will do tons of bugs for sure) can become
|
||||
much easier if you know the huge set of tools provided by your OS. You should also try to develop
|
||||
in the chosen environment. Chose PHP? Get a Linux desktop! ASP.NET? Get a Windows. Everything
|
||||
will be much easier!
|
||||
|
||||
Learn the basics of the web server you are using
|
||||
================================================
|
||||
|
||||
PHP can run on `Apache <http://httpd.apache.org/>`_ (as a module), or any CGI-capable webserver,
|
||||
like `lighttpd <http://www.lighttpd.net/>`_ or `nginx <http://nginx.org/>`_ (well, it can also run
|
||||
on IIS, but trust me: you don’t want that). ASP.NET is designed for IIS, and although some
|
||||
scripts can be run under a mono-capable server, it should still stay there.
|
||||
|
||||
Whichever you choose, learn the basics! How to start and stop the service,
|
||||
basic configuration methods, modules/extensions, and so on. It’s more than sure
|
||||
that you will face some issues while developing, so it can never hurt.
|
||||
|
||||
Keep your versions under control
|
||||
================================
|
||||
|
||||
Version control is critical nowadays. It gives you a basic backup solution, can come in handy
|
||||
with debugging, and if you ever want to work in a team, you will badly need it.
|
||||
|
||||
Subversion is a bit out of date now, and it’s kind of hard to set up.
|
||||
|
||||
Git is no easy. You will have to learn a lot of stuff, but basicly it’s just another version
|
||||
control system. Just choose if you want to stick to merge-then-commit or rebase-then-commit, get
|
||||
a client, and get on the run.
|
||||
|
||||
Microsoft’s Team Foundation is another good way if you are working in a team. It provides several
|
||||
other features besides version controlling, and is well integrated into Visual Studio, which is
|
||||
highly recommended for Windows based development.
|
||||
|
||||
Choose an environment to work in
|
||||
================================
|
||||
|
||||
There are so many good tools out there. You should choose according to the language and OS on
|
||||
what you are working on. `Zend Studio <http://www.zend.com/en/products/studio>`_ or `Netbeans
|
||||
<https://netbeans.org/>`_ are both good tools for PHP development, while `Visual Studio
|
||||
<http://www.visualstudio.com/>`_ is a best buy for Windows development. Both of these have many
|
||||
ups and downs, but once you get in touch with their deeper parts, you will like them.
|
17
content/blog/2012-09-10-do-not-track-in-ie10-vs-apache.rst
Normal file
17
content/blog/2012-09-10-do-not-track-in-ie10-vs-apache.rst
Normal file
@@ -0,0 +1,17 @@
|
||||
Do-Not-Track in IE10 vs. Apache
|
||||
###############################
|
||||
|
||||
:date: 2012-09-10 20:22:32Z
|
||||
:category: blog
|
||||
:tags: apache,technology
|
||||
:url: blog/2012/9/10/do-not-track-in-ie10-vs-apache.html
|
||||
:save_as: blog/2012/9/10/do-not-track-in-ie10-vs-apache.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
`Apache developer decided not to accept Do-Not-Track headers from IE10 users
|
||||
<http://arstechnica.com/security/2012/09/apache-webserver-updated-to-ignore-do-not-track-settings-in-ie-10/>`_,
|
||||
because it’s enabled by default. So… if I install a plugin that hides the fact from the web
|
||||
server that I’m using IE10, I become eligible of using it. But if I do this, I simply became
|
||||
eligible because I consciously installed that addon, so I could actually use it without hiding the
|
||||
fact. Sorry if I’m a bit Philosoraptorish…
|
@@ -0,0 +1,74 @@
|
||||
Symfony 2 – Create role- and class-based ACLs with your roles coming from the ORM
|
||||
#################################################################################
|
||||
|
||||
:date: 2012-09-16T18:39:25Z
|
||||
:category: blog
|
||||
:tags: php,symfony
|
||||
:url: blog/2012/9/16/symfony-2-create-role-and-class-based-acls-with-your-roles-coming-from-the-orm.html
|
||||
:save_as: blog/2012/9/16/symfony-2-create-role-and-class-based-acls-with-your-roles-coming-from-the-orm.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
During the last weeks I had some serious issues with one of my private Symfony 2 projects. One of
|
||||
my goals was to create a dynamic security system, e.g my administrators wanted to create roles,
|
||||
and grant these roles access to different object types (classes) and/or objects.
|
||||
|
||||
So I have created a ``User`` entity, which implements ``UserInterface`` and
|
||||
``AdvancedUserInterface``, the latter for the possibility to enable/disable accounts and such. It
|
||||
had a ``$roles`` property, which was a ``ManyToMany`` relation to the ``Role`` entity, which
|
||||
implemented ``RoleInterface``. Also I have created my own role hierarchy service that implements
|
||||
``RoleHierarchyInterface``.
|
||||
|
||||
So far so good, first tests. It soon turned out that if ``User::getRoles()`` returns a
|
||||
``DoctrineCollection`` as it does by default, then the standard
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$this->get('security.context')->isGranted('ROLE_ADMIN');
|
||||
|
||||
doesn’t work. I know, it should not be hard coded, as my roles and permission tables are dynamic,
|
||||
I have just tested. So I fixed my ``User`` entity so ``getRoles()`` returns an array of ``Role``
|
||||
objects instead of the ``DoctrineCollection``. Also I implemented a ``getRolesCollection()``
|
||||
method to return the original collection, but I think it will never be used.
|
||||
|
||||
After that, I had to implement some more features so I put this task away. Then, I tried to
|
||||
create my first ACL.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$securityIdentity = new RoleSecurityIdentity('ROLE_ADMIN');
|
||||
$objectIdentity = new ObjectIdentity('newsClass', 'Acme\\DemoBundle\\Entity\\News');
|
||||
$acl = $aclProvider->createAcl($objectIdentity);
|
||||
$acl->insertClassAce($securityIdentity, MaskBuilder::MASK_OWNER);
|
||||
$aclProvider->updateAcl($acl);
|
||||
|
||||
I was about to check if the user who is logged in has an ``OWNER`` permission on the ``User``
|
||||
class.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$this->objectIdentity = new ObjectIdentity(self::OBJECT_ID, self::OBJECT_FQCN);
|
||||
if ($this->securityContext->isGranted('OWNER', $this->objectIdentity) === false) {
|
||||
throw new AccessDeniedException('You don’t have the required permissions!');
|
||||
}
|
||||
|
||||
The ACL was defined based on a role, so everyone who had the ``ROLE_ADMIN`` role should gain
|
||||
access to the user listing page. But they didn’t. It took several weeks to find the cause, I
|
||||
have put it on `stackoverflow
|
||||
<http://stackoverflow.com/questions/12057795/symfony-2-1-this-getsecurity-context-isgrantedrole-admin-returns-fa>`_
|
||||
and the Symfony Google Group, but no usable answers.
|
||||
|
||||
Then I went off for debugging. Setting up NetBeans for xdebug-based PHP debugging was real fun
|
||||
under Fedora, but that’s another story. After a while I have found that Symfony’s basic access
|
||||
decision manager checks for ``$role->getRole()`` only if ``$role`` is an instance of
|
||||
``Symfony\Component\Security\Core\Role\Role``, instead of checking if the object implements
|
||||
``Symfony\Component\Security\Core\Role\RoleInterface``. So I’ve checked if the bug is already
|
||||
reported. It turned out that it was, and my solution was available in a specific commit about a
|
||||
year ago, but as `Johannes Schmitt commented, it would introduce a security issue
|
||||
<https://github.com/symfony/symfony/commit/af70ac8d777873c49347ac828a817a400006cbea>`_, so it was
|
||||
reverted. Unfortunately neither Johannes Schmitt, nor Fabien Potencier (nor anyone else) could (or
|
||||
wanted) to tell about this issue. So the final (and somewhat hack-like) solution was to extend
|
||||
``Symfony\Component\Security\Core\Role\Role``. And boom! It worked.
|
36
content/blog/2012-10-07-smsgateway-and-smssender.rst
Normal file
36
content/blog/2012-10-07-smsgateway-and-smssender.rst
Normal file
@@ -0,0 +1,36 @@
|
||||
SmsGateway and SmsSender
|
||||
########################
|
||||
|
||||
:date: 2012-10-07T00:10:26Z
|
||||
:category: blog
|
||||
:tags: development,php,symfony
|
||||
:url: blog/2012/10/7/smsgateway-and-smssender.html
|
||||
:save_as: blog/2012/10/7/smsgateway-and-smssender.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I have just uploaded my SmsGateway, SmsSender and SmsSenderBundle packages to `GitHub
|
||||
<http://github.com/gergelypolonkai>`_ and `Packagist <http://packagist.org>`_. I hope some of you
|
||||
will find it useful.
|
||||
|
||||
* SmsGateway
|
||||
|
||||
* `SmsGateway on GitHub`_
|
||||
* `SmsGateway on Packagist`_
|
||||
|
||||
* SmsSender
|
||||
|
||||
* `SmsSender on GitHub`_
|
||||
* `SmsSender on Packagist`_
|
||||
|
||||
* SmsSenderBundle
|
||||
|
||||
* `SmsSenderBundle on GitHub`_
|
||||
* `SmsSenderBundle on Packagist`_
|
||||
|
||||
.. _SmsGateway on GitHub: https://github.com/gergelypolonkai/smsgateway
|
||||
.. _SmsGateway on Packagist: https://packagist.org/packages/gergelypolonkai/smsgateway
|
||||
.. _SmsSender on GitHub: https://github.com/gergelypolonkai/smssender
|
||||
.. _SmsSender on Packagist: https://packagist.org/packages/gergelypolonkai/smssender
|
||||
.. _SmsSenderBundle on GitHub: https://github.com/gergelypolonkai/smssender-bundle
|
||||
.. _SmsSenderBundle on Packagist: https://packagist.org/packages/gergelypolonkai/smssender-bundle
|
@@ -0,0 +1,22 @@
|
||||
Changing the session cookie’s name in Symfony 2
|
||||
###############################################
|
||||
|
||||
:date: 2012-10-13T12:49:28Z
|
||||
:category: blog
|
||||
:tags: symfony,development
|
||||
:url: blog/2012/10/13/changing-the-session-cookie-s-name-in-symfony-2.html
|
||||
:save_as: blog/2012/10/13/changing-the-session-cookie-s-name-in-symfony-2.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I have a development server, on which I have several Symfony 2.x projects under the same hostname
|
||||
in different directories. Now I’m facing a funny problem which is caused by that the cookies
|
||||
Symfony places for each of my projects have the same name.
|
||||
|
||||
To change this, you will have to modify the ``config.yml`` file like this:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
session:
|
||||
name: SiteSpecificSessionName
|
||||
lifetime: 3600
|
@@ -0,0 +1,52 @@
|
||||
Symfony 2 Configuration – Array of associative arrays
|
||||
#####################################################
|
||||
|
||||
:date: 2012-12-20T12:03:23Z
|
||||
:category: blog
|
||||
:tags: php,symfony
|
||||
:url: blog/2012/12/20/symfony-2-configuration-array-of-associative-arrays.html
|
||||
:save_as: blog/2012/12/20/symfony-2-configuration-array-of-associative-arrays.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Few days ago I have struggled with a problem using Symfony2 configuration. I
|
||||
wanted to add the following kind of configuration to ``config.yml``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
acme_demo:
|
||||
transitions:
|
||||
- { hc_cba: 180 }
|
||||
- { cba_hc: -1 }
|
||||
|
||||
The problem was that the stuff under ``transitions`` is dynamic, so those ``hc_cba`` and
|
||||
``cba_hc`` tags can be pretty much anything. After hitting many errors, I came to the solution:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$rootNode
|
||||
->children()
|
||||
->arrayNode('state_machine')
|
||||
->requiresAtLeastOneElement()
|
||||
->beforeNormalization()
|
||||
->ifArray()
|
||||
->then(function($values) {
|
||||
$ret = array();
|
||||
foreach ($values as $value) {
|
||||
foreach ($value as $transition => $time) {
|
||||
$ret[] = array('transition' => $transition, 'time' => e);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
})
|
||||
->end()
|
||||
->prototype('array')
|
||||
->children()
|
||||
->scalarNode('transition')->end()
|
||||
->scalarNode('time')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
13
content/blog/2013-01-05-development-man-pages-on-fedora.rst
Normal file
13
content/blog/2013-01-05-development-man-pages-on-fedora.rst
Normal file
@@ -0,0 +1,13 @@
|
||||
Development man pages on Fedora
|
||||
###############################
|
||||
|
||||
:date: 2013-01-05T18:20:41Z
|
||||
:category: blog
|
||||
:tags: development,fedora
|
||||
:url: blog/2013/1/5/development-man-pages-on-fedora.html
|
||||
:save_as: blog/2013/1/5/development-man-pages-on-fedora.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
If you use Fedora (like me), and can’t find the development manual pages for e.g. ``printf(3)``
|
||||
(like me), just ``yum install man-pages`` (like me).
|
@@ -0,0 +1,99 @@
|
||||
Registering an enum type in GLib’s type system
|
||||
##############################################
|
||||
|
||||
:date: 2013-01-06T02:34:03Z
|
||||
:category: blog
|
||||
:tags: c,development,glib
|
||||
:url: blog/2013/1/6/registering-an-enum-type-in-glib-s-type-system.html
|
||||
:save_as: blog/2013/1/6/registering-an-enum-type-in-glib-s-type-system.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I faced a problem in my `GLib <https://developer.gnome.org/glib/>`_ self-teaching project, `wMUD
|
||||
<https://github.com/gergelypolonkai/wmud>`_ today. I wanted to register a signal for a
|
||||
``GObject``, whose handler should accept two ``enum`` parameters for which I had to register a new
|
||||
``GEnum`` type in the ``GObject`` type system. However, the `documentation on this feature
|
||||
<https://developer.gnome.org/gobject/unstable/gtype-non-instantiable.html>`_ (thanks for pointing
|
||||
out goes to `hashem` on ``#gnome-hackers``) is not… uhm… obvious. Making the long story short, I
|
||||
have checked with the ``GIO`` sources for an example, and using that, I have created this small,
|
||||
working chunk:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#ifndef __WMUD_CLIENT_STATE_H__
|
||||
#define __WMUD_CLIENT_STATE_H__
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
/**
|
||||
* WmudClientState:
|
||||
* @WMUD_CLIENT_STATE_FRESH: Client is newly connected. Waiting for a login
|
||||
* player name
|
||||
* @WMUD_CLIENT_STATE_PASSWAIT: Login player name is entered, waiting for a
|
||||
* login password
|
||||
* @WMUD_CLIENT_STATE_MENU: Authentication was successful, player is now in the
|
||||
* main game menu
|
||||
* @WMUD_CLIENT_STATE_INGAME: Character login was successful, player is now
|
||||
* in-game
|
||||
* @WMUD_CLIENT_STATE_YESNO: Player was asked a yes/no question, and we are
|
||||
* waiting for the answer. client.yesNoCallback MUST be set at this point!
|
||||
* TODO: if wmudClient had a prevState field, and there would be some hooks
|
||||
* that are called before and after the client enters a new state, this
|
||||
* could be a three-state stuff, in which the player can enter e.g ? as
|
||||
* the answer, so they would be presented with the question again.
|
||||
* @WMUD_CLIENT_STATE_REGISTERING: Registering a new player. Waiting for the
|
||||
* e-mail address to be given
|
||||
* @WMUD_CLIENT_STATE_REGEMAIL_CONFIRM: E-mail address entered séms valid,
|
||||
* waiting for confirmation
|
||||
*
|
||||
* Game client states.
|
||||
*/
|
||||
typedef enum {
|
||||
WMUD_CLIENT_STATE_FRESH,
|
||||
WMUD_CLIENT_STATE_PASSWAIT,
|
||||
WMUD_CLIENT_STATE_MENU,
|
||||
WMUD_CLIENT_STATE_INGAME,
|
||||
WMUD_CLIENT_STATE_YESNO,
|
||||
WMUD_CLIENT_STATE_REGISTERING,
|
||||
WMUD_CLIENT_STATE_REGEMAIL_CONFIRM
|
||||
} WmudClientState;
|
||||
|
||||
|
||||
GType wmud_client_state_get_type (void) G_GNUC_CONST;
|
||||
#define WMUD_TYPE_CLIENT_STATE (wmud_client_state_get_type())
|
||||
|
||||
#endif /* __WMUD_CLIENT_STATE_H__ */
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include "wmudclientstate.h"
|
||||
|
||||
GType
|
||||
wmud_client_state_get_type (void)
|
||||
{
|
||||
static volatile gsize g_define_type_id__volatile = 0;
|
||||
|
||||
if (g_once_init_enter(&g_define_type_id__volatile)) {
|
||||
static const GEnumValue values[] = {
|
||||
{ WMUD_CLIENT_STATE_FRESH, "WMUD_CLIENT_STATE_FRESH", "fresh" },
|
||||
{ WMUD_CLIENT_STATE_PASSWAIT, "WMUD_CLIENT_STATE_PASSWAIT", "passwait" },
|
||||
{ WMUD_CLIENT_STATE_MENU, "WMUD_CLIENT_STATE_MENU", "menu" },
|
||||
{ WMUD_CLIENT_STATE_INGAME, "WMUD_CLIENT_STATE_INGAME", "ingame" },
|
||||
{ WMUD_CLIENT_STATE_YESNO, "WMUD_CLIENT_STATE_YESNO", "yesno" },
|
||||
{ WMUD_CLIENT_STATE_REGISTERING, "WMUD_CLIENT_STATE_REGISTERING", "registering" },
|
||||
{ WMUD_CLIENT_STATE_REGEMAIL_CONFIRM, "WMUD_CLIENT_STATE_REGEMAIL_CONFIRM", "regemail-confirm" },
|
||||
{ 0, NULL, NULL }
|
||||
};
|
||||
GType g_define_type_id = g_enum_register_static(g_intern_static_string("WmudClientState"), values);
|
||||
g_once_init_leave(&g_define_type_id__volatile, g_define_type_id);
|
||||
}
|
||||
|
||||
return g_define_type_id__volatile;
|
||||
}
|
||||
|
||||
Still, it can be made more perfect by using the `glib-mkenums
|
||||
<http://developer.gnome.org/gobject/stable/glib-mkenums.html>`_ tool. I will read through the
|
||||
GLib Makefiles tomorrow for some hints on this.
|
||||
|
||||
Edit: you can find the glib-mkenums solution `here
|
||||
<{filename}2014-08-16-registering-an-enum-type-in-glib-glib-mkenums-magic.rst>`_.
|
16
content/blog/2013-01-14-git-rm-cached-madness.rst
Normal file
16
content/blog/2013-01-14-git-rm-cached-madness.rst
Normal file
@@ -0,0 +1,16 @@
|
||||
git rm --cached madness
|
||||
#######################
|
||||
|
||||
:date: 2013-01-14T21:38:00Z
|
||||
:category: blog
|
||||
:tags: development,git
|
||||
:url: blog/2013/1/14/git-rm-cached-madness.html
|
||||
:save_as: blog/2013/1/14/git-rm-cached-madness.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I have recently learned about ``git rm --cached``. It’s a very good tool, as it removes a file
|
||||
from tracking, without removing your local copy of it. However, be warned that if you use ``git
|
||||
pull`` in another working copy, the file will be removed from there! If you accidentally put the
|
||||
configuration of a production project, and remove it on your dev machine, it can cause a lot of
|
||||
trouble ;)
|
@@ -0,0 +1,53 @@
|
||||
JMS\\DiExtraBundle’s GrepPatternFinder – grep exits with status code 2 on Fedora 18
|
||||
###################################################################################
|
||||
|
||||
:date: 2013-01-17T00:32:12Z
|
||||
:category: blog
|
||||
:tags: fedora,selinux,symfony
|
||||
:url: blog/2013/1/17/jms-diextrabundle-s-greppatternfinder-grep-exits-with-status-code-2-on-fedora-18.html
|
||||
:save_as: blog/2013/1/17/jms-diextrabundle-s-greppatternfinder-grep-exits-with-status-code-2-on-fedora-18.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Yesterday I’ve upgraded my development machines from Fedora 17 to Fedora
|
||||
18. Although it went well, my `Symfony <http://symfony.com/>`_ projects stopped working with a
|
||||
message like this:
|
||||
|
||||
.. code-block:: log
|
||||
|
||||
RuntimeException: Command "/usr/bin/grep --fixed-strings --directories=recurse --devices=skip --files-with-matches --with-filename --color=never --include=*.php 'JMS\DiExtraBundle\Annotation'
|
||||
'/var/www/html/gergelypolonkaiweb/app/../src'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/symfony/symfony/src/Symfony/Bundle/SecurityBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/symfony/monolog-bundle/Symfony/Bundle/MonologBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/symfony/swiftmailer-bundle/Symfony/Bundle/SwiftmailerBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/symfony/assetic-bundle/Symfony/Bundle/AsseticBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/doctrine/doctrine-bundle/Doctrine/Bundle/DoctrineBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/sensio/framework-extra-bundle/Sensio/Bundle/FrameworkExtraBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/jms/aop-bundle/JMS/AopBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/jms/security-extra-bundle/JMS/SecurityExtraBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/doctrine/doctrine-migrations-bundle/Doctrine/Bundle/MigrationsBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/friendsofsymfony/jsrouting-bundle/FOS/JsRoutingBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/avalanche123/imagine-bundle/Avalanche/Bundle/ImagineBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/genemu/form-bundle/Genemu/Bundle/FormBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/src/GergelyPolonkai/FrontBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/src/GergelyPolonkai/GeshiBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/symfony/symfony/src/Symfony/Bundle/WebProfilerBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/sensio/distribution-bundle/Sensio/Bundle/DistributionBundle'
|
||||
'/var/www/html/gergelypolonkaiweb/vendor/sensio/generator-bundle/Sensio/Bundle/GeneratorBundle'" exited with non-successful status code "2".
|
||||
|
||||
After getting through my logs and such, I’ve finally found out that the new SELinux policy is
|
||||
causing the trouble together with git. Eventually, my ``.git/logs`` directory is tagged as
|
||||
``unconfined_u:object_r:httpd_log_t:s0``. ``httpd_log_t`` type is not readable by the
|
||||
``system_u:system_r:httpd_t:s0`` user, which makes ``/usr/bin/grep`` throw an access denied error.
|
||||
To fix this, I needed to do
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
semanage fcontext -a -t httpd_sys_content_t '/var/www(/.*)?/\.git/logs(/.*)?'
|
||||
|
||||
as root. This makes ``.git`` directories readable for the httpd process, thus, for ``grep``. The
|
||||
optimal solution would be to tell ``GrepPatternFinder`` to ignore version control stuff, so the
|
||||
``httpd`` process would have no access to them at all. Also, in production, removing the ``.git``
|
||||
or ``.svn`` directories could be a good idea.
|
@@ -0,0 +1,33 @@
|
||||
mount: device or resource busy after enabling multipath
|
||||
#######################################################
|
||||
|
||||
:date: 2013-02-19T23:09:05Z
|
||||
:category: blog
|
||||
:tags: linux,heartbeat-cluster
|
||||
:url: blog/2013/2/19/mount-device-or-resource-busy-after-enabling-multipath.html
|
||||
:save_as: blog/2013/2/19/mount-device-or-resource-busy-after-enabling-multipath.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
We have a heartbeat cluster with two nodes. It has been running for several months without
|
||||
problems. The shared storage is on an IBM DS3400, on which we have a large volume formatted with
|
||||
ext4.
|
||||
|
||||
Today I decided to reboot the active node for security reasons. So I’ve switched to the passive
|
||||
node, which failed at the first step: it was unable to mount the storage (``/dev/sda1``). After
|
||||
whining for a few moments, I tried to mount it by hand, which told me
|
||||
|
||||
.. code-block:: log
|
||||
|
||||
/dev/sda1 already mounted or /data is busy
|
||||
|
||||
I’ve quickly made sure that none of that was true. After checking this-and-that, it turned out
|
||||
that the passive node had ``multipathd`` running, so I looked under ``/dev/mapper``, and found two
|
||||
symlinks there, ``<long-long WWN>`` and ``<long-long WWN>-part1``. As the partition table and the
|
||||
disk size was the same as on ``/dev/sda``, I tried to
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
mount /dev/<long-long WWN>-part1 /data
|
||||
|
||||
and voilà! It worked like charm!
|
@@ -0,0 +1,23 @@
|
||||
Why I stopped using annotation based routing in Symfony today
|
||||
#############################################################
|
||||
|
||||
:date: 2013-02-27T23:10:24Z
|
||||
:category: blog
|
||||
:tags: development,symfony
|
||||
:url: blog/2013/2/27/why-i-stopped-using-annotation-based-routing-in-symfony-today.html
|
||||
:save_as: blog/2013/2/27/why-i-stopped-using-annotation-based-routing-in-symfony-today.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I have read several opinions about routing configuration in Symfony. I stayed with annotation
|
||||
based routing as it was convinient for me to see the URL right above the controller action. This
|
||||
was because by just checking the URL, I remembered the controlling code, as they always were fresh
|
||||
ones. Well, until today.
|
||||
|
||||
I had to take a look into an old (Sf 2.0, last commit was about 3 months ago) project of mine. In
|
||||
the same run I’ve upgraded the whole project to 2.2 (it was a fast one, thanks for `JMikola@GitHub
|
||||
<https://github.com/jmikola>`_ for the quick reply on my issue with `JmikolaJsAssetsHelperBundle
|
||||
<https://github.com/jmikola/JmikolaJsAssetsHelperBundle>`_ again!). After that I went on to the
|
||||
requested change. Now, finding a route in about 40 controller files spread between 3 bundles can
|
||||
really be a pain! So I’ve finished with annotation based routing. It’s still a nice feature,
|
||||
it’s simply not for me.
|
@@ -0,0 +1,41 @@
|
||||
Fedora can’t change Active Directory password via kpasswd
|
||||
#########################################################
|
||||
|
||||
:date: 2013-03-05T08:55:04Z
|
||||
:category: blog
|
||||
:tags: fedora,kerberos,active-directory
|
||||
:url: blog/2013/3/5/fedora-can-t-change-active-directory-password-via-kpasswd.html
|
||||
:save_as: blog/2013/3/5/fedora-can-t-change-active-directory-password-via-kpasswd.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I wanted to change my AD password today. As the AD is actually a Kerberos server, I was pretty
|
||||
sure that ``kpasswd`` will do the trick. However, ``kpasswd`` output looked like this:
|
||||
|
||||
.. code-block:: output
|
||||
|
||||
$ kpasswd
|
||||
Password for polonkai.gergely@EXAMPLE.LOCAL:
|
||||
Enter new password:
|
||||
Enter it again:
|
||||
kpasswd: Cannot find KDC for requested realm changing password
|
||||
|
||||
I’ve checked ``kinit`` and ``klist``, everything looked fine. After a while it came
|
||||
to my mind that password changing is done through the kadmin server, not
|
||||
through the KDC. It seems that when I set up the Active Directory membership,
|
||||
the ``admin_server`` directive is not get written to ``krb5.conf``. So all I had to
|
||||
do was to put
|
||||
|
||||
.. code-block:: conf
|
||||
|
||||
admin_server = ad.example.local
|
||||
|
||||
in that file, and voilà!
|
||||
|
||||
.. code-block:: output
|
||||
|
||||
$ kpasswd
|
||||
Password for polonkai.gergely@EXAMPLE.LOCAL:
|
||||
Enter new password:
|
||||
Enter it again:
|
||||
Password changed.
|
25
content/blog/2013-03-05-haversine-in-mysql.rst
Normal file
25
content/blog/2013-03-05-haversine-in-mysql.rst
Normal file
@@ -0,0 +1,25 @@
|
||||
Haversine in MySQL
|
||||
##################
|
||||
|
||||
:date: 2013-03-05T12:49:28Z
|
||||
:category: blog
|
||||
:tags: mysql,development
|
||||
:url: blog/2013/3/5/haversine-in-mysql.html
|
||||
:save_as: blog/2013/3/5/haversine-in-mysql.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Just insert it in your database, feed them two Google coordinates, and you get the distance in
|
||||
kilometres. If you happen to need it in miles, change the constant ``12756.200`` in the
|
||||
``RETURN`` row to ``7922.6`` instead.
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
DELIMITER $$
|
||||
|
||||
CREATE FUNCTION `haversine` (lng1 FLOAT, lat1 FLOAT, lng2 FLOAT, lat2 FLOAT)
|
||||
RETURNS float NO SQL DETERMINISTIC
|
||||
BEGIN
|
||||
SET @a = ABS(POWER(SIN(RADIANS(lat1 - lat2)) / 2, 2) + COS(RADIANS(lat1)) * COS(RADIANS(lat2)) * POWER(SIN(RADIANS(lng1 - lng2)) / 2, 2));
|
||||
RETURN 12756.200 * ATAN2(SQRT(@a), SQRT(1 - @a));
|
||||
END$$
|
25
content/blog/2013-03-13-dvorak-and-me.rst
Normal file
25
content/blog/2013-03-13-dvorak-and-me.rst
Normal file
@@ -0,0 +1,25 @@
|
||||
Dvorak and me
|
||||
#############
|
||||
|
||||
:date: 2013-03-13T21:20:13Z
|
||||
:category: blog
|
||||
:tags: linux
|
||||
:url: blog/2013/3/13/dvorak-and-me.html
|
||||
:save_as: blog/2013/3/13/dvorak-and-me.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
A few months ago I have decided to switch to the Dvorak layout. After using QWERTY (well, QWERTZ,
|
||||
to be precise) for almost 17 years, it was a hard decision, but now I think it worthed the try. I
|
||||
started with the UK (Dvorak with UK punctuation) layout, and in about four weeks, I’ve almost
|
||||
reached my original typing speed. Today I have modified the Hungarian xkb definitions file to add
|
||||
the Hungarian accended letters like ű to the layout, so I don’t have to use dead keys anymore
|
||||
(which apparently turned out to be a problem, as the Linux version of Java doesn’t support dead
|
||||
keys at all).
|
||||
|
||||
Best thing is, as I never learned proper 10-finger typing, but learned Dvorak that way, I can
|
||||
switch between QWERTY and Dvorak more or less painlessly (about 10 minutes of confusion, so to
|
||||
say).
|
||||
|
||||
Conclusion: I don’t know yet if this was actually a good decision, but it wasn’t bad, after all.
|
||||
But seeing people’s faces when they try to type on my machine totally worths it.
|
25
content/blog/2013-04-09-renaming-a-symfony-2-bundle.rst
Normal file
25
content/blog/2013-04-09-renaming-a-symfony-2-bundle.rst
Normal file
@@ -0,0 +1,25 @@
|
||||
Renaming a Symfony 2 bundle
|
||||
###########################
|
||||
|
||||
:date: 2013-04-09T22:29:48Z
|
||||
:category: blog
|
||||
:tags: development,symfony
|
||||
:url: blog/2013/4/9/renaming-a-symfony-2-bundle.html
|
||||
:save_as: blog/2013/4/9/renaming-a-symfony-2-bundle.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Today I’ve realised that the name I gave to one of my Symfony 2 bundles should be something else.
|
||||
To rename a bundle, one must do four things (at least).
|
||||
|
||||
1. Change the namespace from ``Vendor\OldBundle`` to ``Vendor\NewBundle`` in every PHP class
|
||||
(sounds like pain? It is…)
|
||||
2. Change the name of files and classes. Some files under ``src/Vendor/OldBundle`` (and the
|
||||
classes in them) contain the name of the bundle, like
|
||||
``OldBundle/DependencyInjection/VendorOldBundleExtension.php`` and
|
||||
``OldBundle/VendorOldBundle.php``. You should rename them, or Symfony won’t find the classes
|
||||
defined in them! When done, rename the whole bundle directory either.
|
||||
3. Change the configuration files accordingly, including ``AppKernel.php``. These config files
|
||||
are usually ``routing.yml``, ``services.yml``, and in some cases, ``config.yml``.
|
||||
4. Change the references in other parts of your code. A ``grep -r OldBundle .`` will usually
|
||||
help…
|
@@ -0,0 +1,107 @@
|
||||
Installing OTRS in Fedora 18 with SELinux enabled
|
||||
#################################################
|
||||
|
||||
:date: 2013-05-06T06:01:52Z
|
||||
:category: blog
|
||||
:tags: fedora,selinux,otrs
|
||||
:url: blog/2013/5/6/installing-otrs-in-fedora-18-with-selinux-enabled.html
|
||||
:save_as: blog/2013/5/6/installing-otrs-in-fedora-18-with-selinux-enabled.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I’ve read somewhere in an OTRS installation howto that if you want to install OTRS, you will have
|
||||
to disable SELinux. Well, I won’t.
|
||||
|
||||
During the last few months, I have been using Fedora 18 with SELinux on all of my desktop machines
|
||||
and on my notebook, and I had no problems at all. Meanwhile I got familiar with SELinux itself,
|
||||
and got used to solving problems caused by it. So I started ``tail -f /var/log/httpd/error_log``
|
||||
in one terminal (to see if something Apache related thing appears), ``tail -f
|
||||
/var/log/audit/audit.log`` in another (to see errors caused by SELinux), opened the admin manual
|
||||
at the installation chapter, took a deep breath, and went on.
|
||||
|
||||
Throughout this article, I will refer to OTRS 3.2.6 as OTRS and Fedora 18 (with only “stock”
|
||||
repositories) as Fedora. I assume that you have already installed OTRS in a non-SELinux
|
||||
environment before, and that you have at least some basic knowledge about SELinux, MAC, RBAC, and
|
||||
all the like. I’m installing OTRS in ``/opt/otrs``, so if you install it somewhere else, you will
|
||||
have to modify the paths below. Also, if you happen to install under ``/var/www`` (I wouldn’t
|
||||
recommend it), that directory already has the ``httpd_sys_content_t`` type, so you won’t have to
|
||||
set it explicitly.
|
||||
|
||||
As the first step I have unpacked the archive to ``/opt/otrs``. This directory is the OTRS
|
||||
default, many config files have it hardcoded, and changing it is no easy task.
|
||||
|
||||
Running ``otrs.CheckModules.pl`` gave me a list of missing perl modules. Red Hat and Fedora makes
|
||||
it easy to install these, as you don’t have to know the RPM package name, just the perl module
|
||||
name:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yum install 'perl(Crypt::SSLeay)' \
|
||||
'perl(DBD::Pg)' \
|
||||
'perl(GD)' \
|
||||
'perl(JSON::XS)' \
|
||||
'perl(GD::Text)' \
|
||||
'perl(GD::Graph)' \
|
||||
'perl(Mail::IMAPClient)' \
|
||||
'perl(Net::DNS)' \
|
||||
'perl(PDF::API2)' \
|
||||
'perl(Text::CSV_XS)' \
|
||||
'perl(YAML::XS)'
|
||||
|
||||
I also needed to install ``mod_perl``. Although ``otrs.CheckModules.pl`` didn’t mention it, the
|
||||
default settings use syslog as the logging module, so unless you change it in ``Config.pm``, you
|
||||
will also need to install ``'perl(Unix::Syslog)'``, either.
|
||||
|
||||
The default SELinux policy doesn’t permit any network connection to be initiated by Apache httpd.
|
||||
As OTRS needs to connect to its database, you need to enable it explicitly. In older
|
||||
distributions, the ``httpd_can_network_connect`` was the SELinux boolean for this, but recent
|
||||
installations also have a ``httpd_can_network_connect_db`` flag. As far as I know, this enables
|
||||
all network connections to the well-known database servers’ default port, but I will have to check
|
||||
for it. For me, with a MySQL listening on its standard port, the ``setsebool
|
||||
httpd_can_network_connect_db=1`` command just did it.
|
||||
|
||||
With SELinux enabled, Apache won’t be able to read anything that’s not marked with the
|
||||
``httpd_sys_content_t`` type, nor write anywhere without the ``httpd_sys_rw_content_t`` type. The
|
||||
trivial, quick and dirty solution is to label all the files as ``httpd_sys_rw_content_t``, and let
|
||||
everything go. However, the goal of SELinux is just the opposite of this: grant access only to
|
||||
what is really needed. After many trial-and-error steps, it finally turned out that for OTRS to
|
||||
work correctly, you must set
|
||||
|
||||
* ``httpd_sys_content_t``
|
||||
|
||||
* on ``/opt/otrs/var/httpd/htdocs``
|
||||
|
||||
* ``httpd_script_exec_t``
|
||||
|
||||
* on ``/opt/otrs/bin/cgi-bin``
|
||||
|
||||
* ``httpd_sys_rw_content_t``
|
||||
|
||||
* on ``/opt/otrs/Kernel``
|
||||
* on ``/opt/otrs/var/sessions``
|
||||
* on ``/opt/otrs/var/log`` (unless you use syslog for logging)
|
||||
* on ``/opt/otrs/var/packages`` (this is used only when you download an .opm package)
|
||||
* on ``/opt/otrs/var/stats``
|
||||
* on ``/opt/otrs/var/tmp``
|
||||
* on ``/opt/otrs/bin`` (I wonder why this is required, though)
|
||||
|
||||
To do this, use the following command:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
semanage fcontext -a -t <context> <directory regex>
|
||||
|
||||
Where ``<directory regex>`` is something like ``/opt/otrs/Kernel(/.*)?``. When this is done, all
|
||||
you have to do is running ``restorecon -vR /opt/otrs`` so it will relabel everything with the
|
||||
correct types (you can omit ``-v``, I just like to see what my software do).
|
||||
|
||||
The last thing I faced is that Fedora is more restrictive on reading directories other than
|
||||
``/var/www``. It has a ``Require all denied`` on ``<Directory />``, and a ``Require all granted``
|
||||
on ``<Directory /var/www>``, so ``/opt/otrs/var/httpd/htdocs`` will throw a ``403 Forbidden
|
||||
(client denied by server configuration)`` error. To get rid of this, I had to modify
|
||||
``scripts/apache2-httpd.include.conf`` and add ``Require all granted`` to both the ``cgi-bin`` and
|
||||
``htdocs`` directories.
|
||||
|
||||
As I will have to use OTRS in a production environment soon with SELinux enabled, it is more than
|
||||
sure that this list will change in the near future. As there are no official documentation on
|
||||
this (I haven’t find one yet), I have to do it with the trial-and-error way, so be patient!
|
25
content/blog/2013-09-16-swe-glib-final-release.rst
Normal file
25
content/blog/2013-09-16-swe-glib-final-release.rst
Normal file
@@ -0,0 +1,25 @@
|
||||
SWE-GLib final release
|
||||
######################
|
||||
|
||||
:date: 2013-09-16T21:37:17Z
|
||||
:category: blog
|
||||
:tags: development,astrology
|
||||
:url: blog/2013/9/16/swe-glib-final-release.html
|
||||
:save_as: blog/2013/9/16/swe-glib-final-release.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Few of you may know that I’m interested in astrology. About two months ago I have decided to
|
||||
create an astrologers’ software for the GNOME desktop. Since then, I have contacted Jean-André
|
||||
Santoni, who created a software called `Astrognome <https://code.google.com/p/astrognome/>`_ some
|
||||
years ago. We exchanged some e-mails, and after several weeks of coding, I’m proud to present
|
||||
`SWE-GLib <https://github.com/gergelypolonkai/swe-glib>`_ 1.0.1. This is “just” a library which
|
||||
wraps `Swiss Ephemeris <http://www.astro.com/swisseph/>`_, creating a nice GLib-ish interface
|
||||
around it. See the project page and the built-in GTK-Doc document for more information.
|
||||
|
||||
The astrologer’s software I’m writing will be Astrognome (you can check the `GitHub repository
|
||||
<https://github.com/gergelypolonkai/astrognome>`_ already, thanks for Jean-André for letting me
|
||||
use the name). It is currently in pre-alpha status, but already utilizes SWE-GLib (it just can’t
|
||||
display the results yet). If you happen to be interested in astrology and/or Astrognome, fork the
|
||||
repository and contribute! You can also contact me (or open an enhancement issue on GitHub) if
|
||||
you have any ideas.
|
@@ -0,0 +1,22 @@
|
||||
From Symfony to Django in two days
|
||||
##################################
|
||||
|
||||
:date: 2013-09-24T14:05:22Z
|
||||
:category: blog
|
||||
:tags: development,symfony,django
|
||||
:url: blog/2013/9/24/from-symfony-to-django-in-two-days.html
|
||||
:save_as: blog/2013/9/24/from-symfony-to-django-in-two-days.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I was a Python hater for a long time, although I can’t really tell why. It didn’t fit in my mind,
|
||||
maybe. I was programming in BASIC, Pascal (none of these would come to my mind now, though), C,
|
||||
PHP, Perl, JavaScript, and different shell “languages” like awk, sed or bash.
|
||||
|
||||
After I could not fit my next Symfony app on my cloud server (it is pretty low on storage), I have
|
||||
decided to move slowly to Django. My first task was simple: transition my web page (this one)
|
||||
from PHP + Symfony 2 to Python + Django. The results: the “static” pages are already working, the
|
||||
blog listing is almost ready (some styling issues are still around), only tagging remains. And
|
||||
this is after about 6 hours of work. Oh, and the admin site is included with Django, so I don’t
|
||||
have to port that. I have also decided to finally integrate a comment feature in the Django
|
||||
version.
|
25
content/blog/2013-11-05-first-impressions-of-windows-8.rst
Normal file
25
content/blog/2013-11-05-first-impressions-of-windows-8.rst
Normal file
@@ -0,0 +1,25 @@
|
||||
First impressions of Windows 8
|
||||
##############################
|
||||
|
||||
:date: 2013-11-05T08:14:50Z
|
||||
:category: blog
|
||||
:tags: windows
|
||||
:url: blog/2013/11/5/first-impressions-of-windows-8.html
|
||||
:save_as: blog/2013/11/5/first-impressions-of-windows-8.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Many of you may know my commitment to Linux and Open Source Software. But this doesn’t mean I
|
||||
hate proprietary software like many others do. I think everything has its own place in the world,
|
||||
and this goes for software as well.
|
||||
|
||||
A few days ago I got my hands on a new notebook, thanks to my company. It was shipped with
|
||||
Windows 8 by default, and although I installed Fedora 19 in an instant (which went smoothlessly,
|
||||
even with Secure Boot enabled), I’ve decided to give a try to this new Windows Version.
|
||||
|
||||
Being a heavy Windows 7 user, my first thought was “What the hell is this?” But in a day, I got
|
||||
totally used to it. I don’t miss the Start button at all. The applications already installed
|
||||
were almost enough for me (I still need Office. Maybe I’ll also enroll for Office 365 later…),
|
||||
and the games are great and beautiful too. So overall, this new version may be totally different
|
||||
(by the looks), but it seems almost the same Windows as we know it. So if you don’t freak out by
|
||||
touching something new, go give it a try: don’t instant-remove 8 in favour of 7!
|
@@ -0,0 +1,60 @@
|
||||
List Git branches and their remote tracking branches side by side
|
||||
#################################################################
|
||||
|
||||
:date: 2014-07-18T21:46:45Z
|
||||
:category: blog
|
||||
:tags: git
|
||||
:url: blog/2014/7/18/list-git-branches-and-their-remote-tracking-branches-side-by-side.html
|
||||
:save_as: blog/2014/7/18/list-git-branches-and-their-remote-tracking-branches-side-by-side.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I had a hard time following my own branches in a project. They got pretty numerous, and I wasn’t
|
||||
sure if I pushed them to ``origin`` at all. ``git branch -a`` can list all the branches,
|
||||
including remote ones, but, as my list grew too big, it was impossible to follow it any more.
|
||||
|
||||
Thus, I have created a small script called ``git-branches-with-remotes``, which does the work for
|
||||
me. Its only requirements are git (of course), and the ``column`` command, which is pretty
|
||||
obviously present on every POSIX compliant systems (even OSX).
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
#! /bin/sh
|
||||
|
||||
COLUMN=`which column 2> /dev/null`
|
||||
if test -z $COLUMN
|
||||
then
|
||||
echo "\`column' is not found in PATH. Cannot continue."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
current_branch=`git rev-parse --abbrev-ref HEAD`
|
||||
|
||||
for branch in $(git for-each-ref --shell --format='%(refname)' refs/heads | sed -e s/^\'refs\\/heads\\/// -e s/\'$//)
|
||||
do
|
||||
remote=`git config branch.$branch.remote`
|
||||
merge=`git config branch.$branch.merge | sed -e 's/^refs\/heads\///'`
|
||||
|
||||
[ x"$current_branch" == x"$branch" ] && echo -n '*'
|
||||
|
||||
echo -n "$branch"
|
||||
|
||||
if ! test -z $merge
|
||||
then
|
||||
echo -en "\t"
|
||||
echo -n $remote
|
||||
echo -n /
|
||||
echo -n $merge
|
||||
fi
|
||||
|
||||
echo
|
||||
done | $COLUMN -t
|
||||
|
||||
I just put it in my path, and ``git branches-with-remotes`` does the work!
|
||||
|
||||
Edit (16 August): I have added some code to mark the current branch (if any) with an asterisk.
|
||||
Also, I have put this script `in a gist
|
||||
<https://gist.github.com/gergelypolonkai/8af6a3e86b57dd4c250e>`_.
|
||||
|
||||
Edit (26 February, 2015): It turns out that ``git branch -vv`` shows the same information and some
|
||||
more: it also shows if the branches are diverged, and the first line of the last commit’s message.
|
@@ -0,0 +1,148 @@
|
||||
Registering an enum type in GLib, glib-mkenums magic
|
||||
####################################################
|
||||
|
||||
:date: 2014-08-16T15:10:54Z
|
||||
:category: blog
|
||||
:tags: development,c,glib
|
||||
:url: blog/2014/8/16/registering-an-enum-type-in-glib-glib-mkenums-magic.html
|
||||
:save_as: blog/2014/8/16/registering-an-enum-type-in-glib-glib-mkenums-magic.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
In `this post <{filename}2013-01-06-registering-an-enum-type-in-glib-s-type-system.rst>`_ I said I
|
||||
will get through the GLib Makefiles to add an enum type to GLib in a more sophisticated way.
|
||||
|
||||
In my other project, `SWE-GLib <https://github.com/gergelypolonkai/swe-glib>`_ I already used this
|
||||
method. The following two rules in ``Makefile.am`` create ``gswe-enumtypes.h`` and
|
||||
``gswe-enumtypes.c``.
|
||||
|
||||
.. code-block:: makefile
|
||||
|
||||
gswe_enum_headers = headers-that-contain-enums.h
|
||||
|
||||
gswe-enumtypes.h: $(gswe_enum_headers) gswe-enumtypes.h.template
|
||||
$(GLIB_MKENUMS) --template $(filter %.template,$^) $(filter-out %.template,$^) > \
|
||||
gswe-enumtypes.h.tmp && mv gswe-enumtypes.h.tmp gswe-enumtypes.h
|
||||
gswe-enumtypes.c: $(gswe_enum_headers) gswe-enumtypes.h gswe-enumtypes.c.template
|
||||
$(GLIB_MKENUMS) --template $(filter %.template,$^) $(filter-out %.template,$^) > \
|
||||
gswe-enumtypes.c.tmp && mv gswe-enumtypes.c.tmp gswe-enumtypes.c
|
||||
|
||||
``$(GLIB_MKENUMS)`` is set in ``configure`` using
|
||||
|
||||
.. code-block:: m4
|
||||
|
||||
AC_PATH_PROG([GLIB_MKENUMS], [glib-mkenums])
|
||||
|
||||
This approach requires the GNU Autotools (you can get rid of it by changing ``$(GLIB_MKENUMS)`` to
|
||||
the path to ``glib-mkenums`` binary), and two template files, one for the header and one for the
|
||||
code. ``$(gswe_enum_headers)`` contains a list of all the header files that have enum types
|
||||
defined throughout the project.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/*** BEGIN file-header ***/
|
||||
/* gswe-enumtypes.h - Enumeration types for SWE-GLib
|
||||
*
|
||||
* Copyright © 2013 Gergely Polonkai
|
||||
*
|
||||
* SWE-GLib is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* SWE-GLib is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef __GSWE_ENUM_TYPES_H__
|
||||
#define __GSWE_ENUM_TYPES_H__
|
||||
#include <glib-object.h>
|
||||
|
||||
/*** END file-header ***/
|
||||
|
||||
/*** BEGIN file-production ***/
|
||||
/* enumerations from "@filename@" */
|
||||
|
||||
#include "@filename@"
|
||||
/*** END file-production ***/
|
||||
|
||||
/*** BEGIN value-header ***/
|
||||
GType @enum_name@_get_type(void);
|
||||
#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type())
|
||||
/*** END value-header ***/
|
||||
|
||||
/*** BEGIN file-tail ***/
|
||||
|
||||
#endif /* __GSWE_ENUM_TYPES_H__ */
|
||||
/*** END file-tail ***/
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/*** BEGIN file-header ***/
|
||||
/* gswe-enumtypes.c - Enumeration types for SWE-GLib
|
||||
*
|
||||
* Copyright © 2013 Gergely Polonkai
|
||||
*
|
||||
* SWE-GLib is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* SWE-GLib is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "swe-glib.h"
|
||||
#include "gswe-enumtypes.h"
|
||||
#include "@filename@"
|
||||
|
||||
/*** END file-header ***/
|
||||
|
||||
/*** BEGIN file-production ***/
|
||||
/* enumerations from "@filename@" */
|
||||
/*** END file-production ***/
|
||||
|
||||
/*** BEGIN value-header ***/
|
||||
GType
|
||||
@enum_name@_get_type(void)
|
||||
{
|
||||
static volatile gsize g_define_type_id__volatile = 0;
|
||||
|
||||
gswe_init();
|
||||
|
||||
if (g_once_init_enter(&g;_define_type_id__volatile)) {
|
||||
static const G@Type@Value values[] = {
|
||||
/*** END value-header ***/
|
||||
|
||||
/*** BEGIN value-production ***/
|
||||
{
|
||||
@VALUENAME@,
|
||||
"@VALUENAME@",
|
||||
"@valuenick@"
|
||||
},
|
||||
/*** END value-production ***/
|
||||
|
||||
/*** BEGIN value-tail ***/
|
||||
{ 0, NULL, NULL }
|
||||
};
|
||||
|
||||
GType g_define_type_id = g_@type@_register_static(
|
||||
g_intern_static_string("@EnumName@"),
|
||||
values
|
||||
);
|
||||
|
||||
g_once_init_leave(&g;_define_type_id__volatile, g_define_type_id);
|
||||
}
|
||||
|
||||
return g_define_type_id__volatile;
|
||||
}
|
||||
|
||||
/*** END value-tail ***/
|
14
content/blog/2014-09-17-nyanmacs.rst
Normal file
14
content/blog/2014-09-17-nyanmacs.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
NyanMacs
|
||||
########
|
||||
|
||||
:date: 2014-09-17T12:45:42Z
|
||||
:category: blog
|
||||
:tags: emacs
|
||||
:url: blog/2014/9/17/nyanmacs.html
|
||||
:save_as: blog/2014/9/17/nyanmacs.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I was a Vi/ViM user for years. For several reasons I had to change to Emacs now and then. And
|
||||
then, I found `this <https://github.com/TeMPOraL/nyan-mode/>`_. I surrender. Emacs is just
|
||||
better. (And this addon is working even in plain text mode without graphics)
|
@@ -0,0 +1,40 @@
|
||||
Rounding numbers to N decimals in Emacs
|
||||
#######################################
|
||||
|
||||
:date: 2014-10-07T10:28:50Z
|
||||
:category: blog
|
||||
:tags: emacs,development
|
||||
:url: blog/2014/10/7/rounding-numbers-to-n-decimals-in-emacs.html
|
||||
:save_as: blog/2014/10/7/rounding-numbers-to-n-decimals-in-emacs.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I have recently faced a problem, where I had a bunch of SVG files with a large amount of fraction
|
||||
numbers in the path definitions. These images were displayed in small size, so this amount of
|
||||
precision was irrelevant, and these numbers took almost half of my SVG images’ size. So I created
|
||||
an Elisp defun to round these numbers to 2 decimals:
|
||||
|
||||
.. code-block:: lisp
|
||||
|
||||
(defun get-number-at-point ()
|
||||
(interactive)
|
||||
(skip-chars-backward "0123456789.-")
|
||||
(or (looking-at "[0123456789.-]+")
|
||||
(error "No number at point"))
|
||||
(string-to-number (match-string 0)))
|
||||
|
||||
(defun round-number-at-point-to-decimals (decimal-count)
|
||||
(interactive "NDecimal count: ")
|
||||
(let ((mult (expt 10 decimal-count)))
|
||||
(replace-match (number-to-string
|
||||
(/
|
||||
(fround
|
||||
(*
|
||||
mult
|
||||
(get-number-at-point)))
|
||||
mult)))))
|
||||
|
||||
This finds the first digit of the number under point (the cursor), and reduces its digits to the
|
||||
given amount (or the number given with :kbd:`C-u`). It has some drawbacks, though, as it cannot
|
||||
handle exponential forms (e.g. ``1e-1234``), but these were rare in my case, and its hard to
|
||||
iterate through all numbers. I will come over this latter problem soon(ish).
|
@@ -0,0 +1,44 @@
|
||||
Using Git bisect to find the first good commit
|
||||
##############################################
|
||||
|
||||
:date: 2015-02-26T10:42:56Z
|
||||
:category: blog
|
||||
:tags: git
|
||||
:url: blog/2015/2/26/using-git-bisect-to-find-the-first-good-commit.html
|
||||
:save_as: blog/2015/2/26/using-git-bisect-to-find-the-first-good-commit.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Few months ago we “implemented” a bug in our software, which was released to the customers. We
|
||||
continued development for two weeks when the first customer ticket arrived about the bug. We
|
||||
successfully reproduced it with the customer’s version, but not with the development sources; it
|
||||
turned out that one of the developers unconsciously fixed the bug. The devs spent some hours
|
||||
finding where the fix lied before coming to me like “There is ``git-bisect`` which we can use to
|
||||
find the commit where we messed up things. Is there a way to find where we fixed it?”
|
||||
|
||||
For those who don’t know this feature, you have to mark a known “good” and “bad” commit, then
|
||||
git-bisect will go through the commits between this two, present you the corresponding snapshots,
|
||||
and you have to mark each of them as “good” or “bad”. At the end, you will get a commit hash
|
||||
where the bug first occured.
|
||||
|
||||
As it turned out, our developers’ problem rooted in the naming convention of git-bisect: they
|
||||
assumed that the “good” commit must be a working one, while a “bad” one must be the buggy. In
|
||||
this case, we did the following:
|
||||
|
||||
The commit with the customer’s release tag was marked as good (even though this had the bug), and
|
||||
the latest commit on our development branch was marked as “bad” (even though the bug was fixed by
|
||||
then). Now with every snapshot presented by git-bisect we had to do the opposite what you usually
|
||||
do: mark commits still having the bug as “good”, and commits that don’t as “bad”. At the end, we
|
||||
had the hash of the commit that fixed the bug (among some other things; luckily, the developer who
|
||||
pushed that commit had a workflow that introduced a lot of cherry-picking and squashing before the
|
||||
push, so he could easily find the bit that actually fixed the problem in his local repository with
|
||||
the same technique).
|
||||
|
||||
`This StackOverflow answer <http://stackoverflow.com/a/17153598/1305139>`_ suggests the very same,
|
||||
but with some aliases:
|
||||
|
||||
.. code-block:: dosini
|
||||
|
||||
[alias]
|
||||
bisect-fixed = bisect bad
|
||||
bisect-unfixed = bisect good
|
@@ -0,0 +1,19 @@
|
||||
Good bye, Digital Ocean! Hello again, GitHub!
|
||||
#############################################
|
||||
|
||||
:date: 2015-04-25T21:18:56Z
|
||||
:category: blog
|
||||
:url: blog/2015/4/25/good-bye-digital-ocean-hello-again-github.html
|
||||
:save_as: blog/2015/4/25/good-bye-digital-ocean-hello-again-github.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Few years ago I have signed up for a `Digital Ocean <https://www.digitalocean.com/>`_ account. I
|
||||
used one single droplet for my private needs, like hosting my private Git repositories and my
|
||||
blog. However, as I didn’t host anything else there except my blog, I decided to shut it down.
|
||||
From now on, my blog is on `GitHub Pages <https://pages.github.com/>`_, as it provides just
|
||||
everything I need (except automatically converting my resume to PDF. But I can live without
|
||||
that.)
|
||||
|
||||
I’m really sorry, Digital Ocean Guys, your hosting is awesome and I’ll keep recommending you to
|
||||
others, but paying for a droplet for one single blog is overkill.
|
@@ -0,0 +1,60 @@
|
||||
Cross browser border-radius SASS mixin with varargs
|
||||
###################################################
|
||||
|
||||
:date: 2015-04-27T22:59:56Z
|
||||
:category: blog
|
||||
:tags: css,sass
|
||||
:url: blog/2015/4/28/cross-browser-border-radius-sass-mixin-with-varargs.html
|
||||
:save_as: blog/2015/4/28/cross-browser-border-radius-sass-mixin-with-varargs.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Few days ago I needed to create style sheets with many rounded boxes, where different corners had
|
||||
to be rounded differently (think about Bootstrap’s `button groups
|
||||
<http://getbootstrap.com/components/#btn-groups>`_).
|
||||
|
||||
CSS has this nifty shorthand to specify border width in one line, like with ``border-width: 1px
|
||||
2px 3px 4px``, but it lacks the same for ``border-radius``. So I decided to create something
|
||||
similar using `Sass mixins <http://sass-lang.com/guide#topic-6>`_ with dynamic parameters.
|
||||
Another nice feature you get using the ``border-width`` shorthand is that you can specify less
|
||||
than four parameters, and the values will be applied on different sides of your box, so in the end
|
||||
all side will have the whole ``border-width`` set.
|
||||
|
||||
I wanted to achieve the same for my ``border-radius`` mixin, although I
|
||||
could not start specifically with the `top` side. I decided to go with
|
||||
the top right corner for the first parameter, while trying to keep a
|
||||
sane repeating pattern. Here is the result:
|
||||
|
||||
.. code-block:: sass
|
||||
|
||||
=border-width($t, $r: $t, $b: $t, $l: $r)
|
||||
border-top-width: $t
|
||||
border-right-width: $r
|
||||
border-bottom-width: $b
|
||||
border-left-width: $l
|
||||
|
||||
=border-top-right-radius($value)
|
||||
border-top-right-radius: $value
|
||||
-moz-border-top-right-radius: $value
|
||||
-webkit-border-top-right-radius: $value
|
||||
|
||||
=border-top-left-radius($value)
|
||||
border-top-left-radius: $value
|
||||
-moz-border-top-left-radius: $value
|
||||
-webkit-border-top-left-radius: $value
|
||||
|
||||
=border-bottom-right-radius($value)
|
||||
border-bottom-right-radius: $value
|
||||
-moz-border-bottom-right-radius: $value
|
||||
-webkit-border-bottom-right-radius: $value
|
||||
|
||||
=border-bottom-left-radius($value)
|
||||
border-bottom-left-radius: $value
|
||||
-moz-border-bottom-left-radius: $value
|
||||
-webkit-border-bottom-left-radius: $value
|
||||
|
||||
=border-radius($tr, $br: $tr, $bl: $br, $tl: $tr)
|
||||
+border-top-right-radius($tr)
|
||||
+border-bottom-right-radius($br)
|
||||
+border-bottom-left-radius($bl)
|
||||
+border-top-left-radius($tl)
|
154
content/blog/2015-06-07-paramconverter-a-la-django.rst
Normal file
154
content/blog/2015-06-07-paramconverter-a-la-django.rst
Normal file
@@ -0,0 +1,154 @@
|
||||
@ParamConverter à la Django
|
||||
###########################
|
||||
|
||||
:date: 2015-06-07T18:14:32Z
|
||||
:category: blog
|
||||
:tags: python,django
|
||||
:url: 2015/06/07/paramconverter-a-la-django/
|
||||
:save_as: 2015/06/07/paramconverter-a-la-django/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
One thing I really miss from `Django <https://www.djangoproject.com/>`_ is `Symfony
|
||||
<http://symfony.com/>`_’s `@ParamConverter
|
||||
<http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html>`_. It
|
||||
made my life so much easier while developing with Symfony. In Django, of course, there is
|
||||
`get_object_or_404
|
||||
<https://docs.djangoproject.com/en/dev/topics/http/shortcuts/#get-object-or-404>`_ but, for
|
||||
example, in one of my projects I had a view that had to resolve 6(!) objects from the URL, and
|
||||
writing ``get_object_or_404`` six times is not what a programmer likes to do (yes, this view had a
|
||||
refactor later on). A quick Google search gave me one `usable result
|
||||
<http://openclassrooms.com/forum/sujet/middleware-django-genre-paramconverter-doctrine>`_ (in
|
||||
French), but it was very generalized that I cannot always use. Also, it was using a middleware,
|
||||
which may introduce performance issues sometimes :sup:`[citation needed]`. So I decided to go
|
||||
with decorators, and at the end, I came up with this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import re
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db import models
|
||||
|
||||
def convert_params(*params_to_convert, **options):
|
||||
"""
|
||||
Convert parameters to objects. Each parameter to this decorator
|
||||
must be a model instance (subclass of django.db.models.Model) or a
|
||||
tuple with the following members:
|
||||
* model: a Model subclass
|
||||
* param_name: the name of the parameter that holds the value to be
|
||||
matched. If not exists, or is None, the model’s class name will
|
||||
be converted from ModelName to model_name form, suffixed with
|
||||
"_id". E.g. for MyModel, the default will be my_model_id
|
||||
* the field name against which the value in param_name will be
|
||||
matched. If not exists or is None, the default will be "id"
|
||||
* obj_param_name: the name of the parameter that will hold the
|
||||
resolved object. If not exists or None, the default value will
|
||||
be the model’s class name converted from ModelName to model_name
|
||||
form, e.g. for MyModel, the default value will be my_model.
|
||||
The values are resolved with get_object_or_404, so if the given
|
||||
object doesn’t exist, it will redirect to a 404 page. If you want
|
||||
to allow non-existing models, pass prevent_404=True as a keyword
|
||||
argument.
|
||||
"""
|
||||
|
||||
prevent_404 = options.pop('prevent_404', False)
|
||||
|
||||
def is_model(m):
|
||||
return issubclass(type(m), models.base.ModelBase)
|
||||
|
||||
if len(params_to_convert) == 0:
|
||||
raise ValueError("Must pass at least one parameter spec!")
|
||||
|
||||
if (
|
||||
len(params_to_convert) == 1 and \
|
||||
hasattr(params_to_convert[0], '__call__') and \
|
||||
not is_model(params_to_convert[0])):
|
||||
raise ValueError("This decorator must have arguments!")
|
||||
|
||||
def convert_params_decorator(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
converted_params = ()
|
||||
for pspec in params_to_convert:
|
||||
# If the current pspec is not a tuple, let’s assume
|
||||
# it’s a model class
|
||||
if not isinstance(pspec, tuple):
|
||||
pspec = (pspec,)
|
||||
|
||||
# First, and the only required element in the
|
||||
# parameters is the model name which this object
|
||||
# belongs to
|
||||
model = pspec[0]
|
||||
|
||||
if not is_model(model):
|
||||
raise ValueError(
|
||||
"First value in pspec must be a Model subclass!")
|
||||
|
||||
# We will calculate these soon…
|
||||
param_name = None
|
||||
calc_obj_name = re.sub(
|
||||
'([a-z0-9])([A-Z])',
|
||||
r'\1_\2',
|
||||
re.sub(
|
||||
'(.)([A-Z][a-z]+)',
|
||||
r'\1_\2',
|
||||
model.__name__)).lower()
|
||||
obj_field_name = None
|
||||
|
||||
# The second element, if not None, is the keyword
|
||||
# parameter name that holds the value to convert
|
||||
if len(pspec) < 2 or pspec[1] is None:
|
||||
param_name = calc_obj_name + '_id'
|
||||
else:
|
||||
param_name = pspec[1]
|
||||
|
||||
if param_name in converted_params:
|
||||
raise ValueError('%s is already converted' % param_name)
|
||||
|
||||
converted_params += (param_name,)
|
||||
field_value = kwargs.pop(param_name)
|
||||
|
||||
# The third element is the field name which must be
|
||||
# equal to the specified value. If it doesn’t exist or
|
||||
# None, it defaults to 'id'
|
||||
if (len(pspec) < 3) or pspec[2] is None:
|
||||
obj_field_name = 'id'
|
||||
else:
|
||||
obj_field_name = pspec[2]
|
||||
|
||||
# The fourth element is the parameter name for the
|
||||
# object. If the parameter already exists, we consider
|
||||
# it an error
|
||||
if (len(pspec) < 4) or pspec[3] is None:
|
||||
obj_param_name = calc_obj_name
|
||||
else:
|
||||
obj_param_name = pspec[3]
|
||||
|
||||
if obj_param_name in kwargs:
|
||||
raise KeyError(
|
||||
"'%s' already exists as a parameter" % obj_param_name)
|
||||
|
||||
filter_kwargs = {obj_field_name: field_value}
|
||||
|
||||
if (prevent_404):
|
||||
kwargs[obj_param_name] = model.objects.filter(
|
||||
**filter_kwargs).first()
|
||||
else:
|
||||
kwargs[obj_param_name] = get_object_or_404(
|
||||
model,
|
||||
**filter_kwargs)
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return convert_params_decorator
|
||||
|
||||
Now I can decorate my views, either class or function based, with ``@convert_params(User,
|
||||
(Article, 'aid'), (Paragraph, None, 'pid'), (AnotherObject, None, None, 'obj'))`` and all the
|
||||
magic happens in the background. The ``user_id`` parameter passed to my function will be popped
|
||||
off, and be resolved against the ``User`` model by using the ``id`` field; the result is put in
|
||||
the new ``user`` parameter. For Article, the ``aid`` parameter will be matched against the ``id``
|
||||
field of the ``Article`` model putting the result into ``article``, and finally, the
|
||||
``another_object_id`` will be matched against the ``id`` field of the ``AnotherObject`` model, but
|
||||
in this case, the result is passed to the original function as ``obj``.
|
17
content/blog/2015-08-19-foss-failmeter.rst
Normal file
17
content/blog/2015-08-19-foss-failmeter.rst
Normal file
@@ -0,0 +1,17 @@
|
||||
F/OSS Fail meter
|
||||
################
|
||||
|
||||
:date: 2015-08-19T10:12:19Z
|
||||
:category: blog
|
||||
:tags: development
|
||||
:url: 2015/08/19/foss-failmeter/
|
||||
:save_as: 2015/08/19/foss-failmeter/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I have recently bumped into `this article <http://spot.livejournal.com/308370.html>`_. Naturally,
|
||||
I quickly calculated the FAIL metrics for all my projects (most of them are pretty high). To ease
|
||||
calculation, I made up a `small page <{static}../failmeter/index.html>`_ based on this list
|
||||
(although I have divided the points by 5; I really don’t understand why spot is using such big
|
||||
points if all of them can be divided by 5). Feel free to use it, and if you have any
|
||||
recommendations (point additions/removal, new categories, etc.), leave me a comment!
|
201
content/blog/2015-08-27-how-my-email-gets-to-that-other-guy.rst
Normal file
201
content/blog/2015-08-27-how-my-email-gets-to-that-other-guy.rst
Normal file
@@ -0,0 +1,201 @@
|
||||
How my e-mail gets to that other guy?
|
||||
#####################################
|
||||
|
||||
:date: 2015-08-27T21:47:19Z
|
||||
:category: blog
|
||||
:tags: technology
|
||||
:url: 2015/08/27/how-my-email-gets-to-that-other-guy/
|
||||
:save_as: 2015/08/27/how-my-email-gets-to-that-other-guy/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
A friend of mine asked me how it is possible that she pushes buttons on her keyboard and mouse,
|
||||
and in an instant her peer reads the text she had in her mind. This is a step-by-step
|
||||
introduction of what happens in-between.
|
||||
|
||||
From your mind to your computer
|
||||
===============================
|
||||
|
||||
When you decide to write an e-mail to an acquaintance of yours, you open up your mailing software
|
||||
(this document doesn’t cover using mail applications you access through your browsers, just plain
|
||||
old Thunderbird, Outlook or similar programs. However, it gets the same after the mail left your
|
||||
computer), and press the “New Mail” button. What happens during this process is not covered in
|
||||
this article, but feel free to ask me in a comment! Now that you have your Mail User Agent (MUA)
|
||||
up and running, you begin typing.
|
||||
|
||||
When you press a button on your keyboard or mouse, a bunch of bits gets through the wire (or
|
||||
through air, if you went wireless) and get into your computer. I guess you learned about Morse
|
||||
during school; imagine two `Morse operators
|
||||
<http://www.uscupstate.edu/academics/education/aam/lessons/susan_sawyer/morse%20code.jpg>`_, one
|
||||
in your keyboard/mouse, and one in your computer. Whenever you press a key, that tiny creature
|
||||
sends a series of short and long beeps (called 0 or 1 bits, respectively) to the operator in your
|
||||
computer (fun fact: have you ever seen someone typing at an amazing speed of 5 key presses per
|
||||
second? Now imagine that whenever that guy presses a key on their keyboard, that tiny little
|
||||
Morse operator pressing his button 16 times for each key press, with perfect timing so that the
|
||||
receiving operator can decide if that was a short or long beep.)
|
||||
|
||||
Now that the code got to the operator inside the machine, it’s up to him to decode it. The funny
|
||||
thing about keyboards and computers is that the computer doesn’t receive the message “Letter Q was
|
||||
pressed”, but instead “The second button on the second row was pressed” (a number called scan
|
||||
code). At this time the operator decodes this information (in this example it is most likely this
|
||||
Morse code: ``···-···· -··-····``) and checks one of his tables titled “Current Keyboard Layout.”
|
||||
It says this specific key corresponds to letter ‘Q’, so it forwards this information (I mean the
|
||||
letter; after this step your computer doesn’t care which plastic slab you hit, just the letter
|
||||
‘Q’) to your MUA, inserts it into the mail in its memory, then displaying it happily (more about
|
||||
this step later).
|
||||
|
||||
When you finish your letter you press the send button of your MUA. First it converts all the
|
||||
pretty letters and pictures to something a computer can understand (yes, those Morse codes, or
|
||||
more precisely, zeros and ones, again). Then it adds loads of meta data, like your name and
|
||||
e-mail address, the current date and time including the time zone and pass it to the sending parts
|
||||
of the MUA so the next step can begin.
|
||||
|
||||
IP addresses, DNS and protocols
|
||||
===============================
|
||||
|
||||
The Internet is a huge amount of computers connected with each other, all of them having at least
|
||||
one address called IP address that looks something like this: ``123.234.112.221``. These are four
|
||||
numbers between 0 and 255 inclusive, separated by dots. This makes it possible to have
|
||||
4,294,967,296 computers. With the rules of address assignment added, this is actually reduced to
|
||||
3,702,258,432; a huge number, still, but it is not enough, as in the era of the Internet of Things
|
||||
everything is interconnected, up to and possibly including your toaster. Thus, we are slowly
|
||||
transitioning to a new addressing scheme that looks like this:
|
||||
``1234:5678:90ab:dead:beef:9876:5432:1234``. This gives an enormous amount of
|
||||
340,282,366,920,938,463,463,374,607,431,768,211,456 addresses, with only
|
||||
4,325,185,976,917,036,918,000,125,705,034,137,602 of them being reserved, which gives us only a
|
||||
petty 335,957,180,944,021,426,545,374,481,726,734,073,854 available.
|
||||
|
||||
Imagine a large city with `that many buildings
|
||||
<http://www.digitallifeplus.com/wp-content/uploads/2012/07/new-york-city-aerial-5.jpg>`_, all of
|
||||
them having only a number: their IP address. No street names, no company names, no nothing. But
|
||||
people tend to be bad at memorizing numbers, so they started to give these buildings names. For
|
||||
example there is a house with the number ``216.58.209.165``, but between each other, people call
|
||||
it ``gmail.com``. Much better, isn’t it? Unfortunately, when computers talk, they only
|
||||
understand numbers so we have to provide them just that.
|
||||
|
||||
As remembering this huge number of addresses is a bit inconvenient, we created Domain Name
|
||||
Service, or DNS for short. A “domain name” usually (but not always) consist of two strings of
|
||||
letters, separated by dots (e.g. polonkai.eu, gmail.com, my-very-long-domain.co.uk, etc.), and a
|
||||
hostname is a domain name occasionally prefixed with something (e.g. **www**.gmail.com,
|
||||
**my-server**.my-very-long-domain.co.uk, etc.) One of the main jobs of DNS is to keep record of
|
||||
hostname/address pairs. When you enter ``gmail.com`` (which happens to be both a domain name and a
|
||||
hostname) in your browser’s address bar, your computer asks the DNS service if it knows the actual
|
||||
address of the building that people call ``gmail.com``. If it does, it will happily tell your
|
||||
computer the number of that building.
|
||||
|
||||
Another DNS job is to store some meta data about these domain names. For such meta data there are
|
||||
record types, one of these types being the Mail eXchanger, or MX. This record of a domain tells
|
||||
the world who is handling incoming mails for the specified domain. For ``gmail.com`` this is
|
||||
``gmail-smtp-in.l.google.com`` (among others; there can be multiple records of the same type, in
|
||||
which case they usually have priorities, too.)
|
||||
|
||||
One more rule: when two computers talk to each other they use so called protocols. These
|
||||
protocols define a set of rules on how they should communicate; this includes message formatting,
|
||||
special code words and such.
|
||||
|
||||
From your computer to the mail server
|
||||
=====================================
|
||||
|
||||
Your MUA has two settings called SMTP server address SMTP port number (see about that later).
|
||||
SMTP stands for Simple Mail Transfer Protocol, and defines the rules on how your MUA, or another
|
||||
mail handling computer should communicate with a mail handling computer when *sending* mail. Most
|
||||
probably your Internet Service Provider gave you an SMTP server name, like ``smtp.aol.com`` and a
|
||||
port number like ``587``.
|
||||
|
||||
When you hit that send button of yours, your computer will check with the DNS service for the
|
||||
address of the SMTP server, which, for ``smtp.aol.com``, is ``64.12.88.133``. The computer puts
|
||||
this name/address pair into its memory, so it doesn’t have to ask the DNS again (this technique is
|
||||
called caching and is widely used wherever time consuming operations happen).
|
||||
|
||||
Then it will send your message to the given port number of this newly fetched address. If you
|
||||
imagined computers as office buildings, you can imagine port numbers as departments and there can
|
||||
be 65535 of them in one building. The port number of SMTP is usually 25, 465 or 587 depending on
|
||||
many things we don’t cover here. Your MUA prepares your letter, adding your e-mail address and
|
||||
the recipients’, together with other information that may be useful for transferring your mail.
|
||||
It then puts this well formatted message in an envelope and writes “to building ``64.12.88.133``,
|
||||
dept. ``587``”, and puts it on the wire so it gets there (if the wire is broken, the building does
|
||||
not exist or there is no such department, you will get an error message from your MUA). Your
|
||||
address and the recipient’s address are inside the envelope; other than the MUA, your own computer
|
||||
is not concerned about it.
|
||||
|
||||
The mailing department (or instead lets call it the Mail Transfer Agent, A.K.A. MTA) now opens
|
||||
this envelope and reads the letter. All of it, letter by letter, checking if your MUA formatted
|
||||
it well. More than probably it also runs your message through several filters to decide if you
|
||||
are a bad guy sending some unwanted letter (also known as spam), but most importantly it fetches
|
||||
the recipients address. It is possible, e.g. when you send an e-mail within the same
|
||||
organization, that the recipient’s address is handled by this very same computer. In this case
|
||||
the MTA puts the mail to the recipient’s mailbox and the next step is skipped.
|
||||
|
||||
From one server to another
|
||||
==========================
|
||||
|
||||
Naturally, it is possible to send an e-mail from one company to another, so these MTAs don’t just
|
||||
wait for e-mails from you, but also communicate with each other. When you send a letter from your
|
||||
``example@aol.com`` address to me at ``gergely@polonkai.eu``, this is what happens.
|
||||
|
||||
In this case, the MTA that initially received the e-mail from you (which happened to be your
|
||||
Internet Service Provider’s SMTP server) turns to the DNS again. It will ask for the MX record of
|
||||
the domain name specified by the e-mail address, (the part after the ``@`` character, in my case,
|
||||
``polonkai.eu``), because the server mentioned that must be contacted, so they can deliver your
|
||||
mail for me. My domain is configured so its primary MX record is ``aspmx.l.google.com`` and the
|
||||
secondary is ``alt1.aspmx.l.google.com`` (and 5 more. Google likes to play it safe.) The MTA
|
||||
then gets the first server name, asks the DNS for its address, and tries to send a message to the
|
||||
``173.194.67.27`` (the address of ``aspmx.l.google.com``), same department. But unlike your MUA,
|
||||
MTAs don’t have a pre-defined port number for other MTAs (although there can be exceptions).
|
||||
Instead, they use well-known port numbers, ``465`` and ``25``. If the MTA on that server cannot
|
||||
be contacted for any reason, it tries the next one on the list of MX records. If none of the
|
||||
servers can be contacted, it will retry based on a set of rules defined by the administrators,
|
||||
which usually means it will retry after 1, 4, 24 and 48 hours. If there is still no answer after
|
||||
that many attempts, you will get an error message back, in the form of an e-mail sent directly by
|
||||
the SMTP server.
|
||||
|
||||
Once the other MTA could be contacted, your message is sent there. The original envelope you used
|
||||
is discarded, and a new one is used with the address and dept. number (port) of the receiving MTA.
|
||||
Also, your message gets altered a little bit, as most MTAs are kind enough (ie. not sneaky) to add
|
||||
a clause to your message stating “the MTA at <organization> has checked and forwarded this
|
||||
message.”
|
||||
|
||||
It is possible, though not likely, that your message gets through more than two MTAs (one at your
|
||||
ISP and one at the receiver’s) before arriving to its destination. At the end, an MTA will say
|
||||
that “OK, this recipient address is handled by me”, your message stops and stays there, put in
|
||||
your peer’s mailbox.
|
||||
|
||||
The mailbox
|
||||
-----------
|
||||
|
||||
Now that the MTA has passed your mail to the mailbox team (I call it a team instead of department
|
||||
because the tasks described here are usually handled by the MTA, too), it reads it. (Pesky little
|
||||
guys these mail handling departments, aren’t they?) If the mailbox has some filtering rules, like
|
||||
“if XY sends me a letter, mark it as important” or “if the letter has a specific word in its
|
||||
subject, put it in the XY folder”, it executes them, but the main point is to land the message in
|
||||
the actual post box of the recipient.
|
||||
|
||||
From the post box to the recipients computer
|
||||
============================================
|
||||
|
||||
When the recipient opens their MUA, it will look to a setting usually called “Incoming mail
|
||||
server”. Just like the SMTP server, it has a name and port number, along with a server type.
|
||||
This type can vary from provider to provider, and is usually one of POP3 (pretty old protocol,
|
||||
doesn’t even support folders on its own), IMAP (a newer one, with folders and message flags like
|
||||
“important”), MAPI (a dialect of IMAP, created by Microsoft as far as I know), or plain old mbox
|
||||
files on the receiving computer (this last option is pretty rare nowadays, so I don’t cover this
|
||||
option. Also, if you use these, you most probably don’t really need this article to understand
|
||||
how these things work.) This latter setting defines the protocol, telling your MUA how to “speak”
|
||||
to the post box.
|
||||
|
||||
So your MUA turns to the DNS once more to get the address of your incoming mail server and
|
||||
contacts it, using the protocol set by the server type. At the end, the recipients computer will
|
||||
receive a bunch of envelopes including the one that contains your message. The MUA opens them one
|
||||
by one and reads them, making a list ordered by their sender or subject, or the date of sending.
|
||||
|
||||
From the recipient’s comupter to their eyes
|
||||
===========================================
|
||||
|
||||
When the recipient then clicks on one of these mails, the MUA will fetch all the relevant bits
|
||||
like the sender, the subject line, the date of sending and the contents itself and sends it to the
|
||||
“printing” department (I use quotes as they don’t really print your mail on paper, they just
|
||||
convert it to a nice image so the recipient can see it. This is sometimes referred to as a
|
||||
rendering engine). Based on a bunch of rules they pretty-print it and send it to your display as
|
||||
a new series of Morse codes. Your display then decides how it will present it to the user: draw
|
||||
the pretty pictures if it is a computer screen, or just raise and lower some hard dots that
|
||||
represents letters on a Braille terminal.
|
@@ -0,0 +1,131 @@
|
||||
Emacs: Implement a GObject’s virtual function
|
||||
#############################################
|
||||
|
||||
:date: 2016-01-13T13:31:12Z
|
||||
:category: blog
|
||||
:tags: c,development,emacs
|
||||
:url: 2016/01/13/emacs-implement-a-gobject-s-virtual-function/
|
||||
:save_as: 2016/01/13/emacs-implement-a-gobject-s-virtual-function/index.html
|
||||
:status: published
|
||||
:author: "Gergely Polonkai"
|
||||
|
||||
I have recently started creating a GLib implementation of the Matrix.org API. For that, I have
|
||||
created a GObject interface, MatrixAPI, which has as many virtual functions as API calls (which is
|
||||
a lot, and expanding). This way I ended up with the following scenario.
|
||||
|
||||
In ``matrix-api.h`` I had a struct like this, with a lot more elements:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
typedef struct {
|
||||
void (*initial_sync)(MatrixAPI *api,
|
||||
MatrixAPICallback callback,
|
||||
gpointer user_data,
|
||||
GError **error);
|
||||
void (*sync)(MatrixAPI *api,
|
||||
MatrixAPICallback callback,
|
||||
gpointer user_data,
|
||||
GError **error);
|
||||
…
|
||||
|
||||
And in ``matrix-http-api.c``, which implements ``MatrixAPI``, I have a function like this (again,
|
||||
with a lot more elements):
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
static void
|
||||
matrix_http_api_matrix_api_init(GObjectInterface *iface)
|
||||
{
|
||||
iface->initial_sync = i_initial_sync;
|
||||
iface->sync = i_sync;
|
||||
…
|
||||
}
|
||||
|
||||
And every time I wanted to implement a new function from the vtable, I had to copy the prototype,
|
||||
and add an ``iface->foo_bar = i_foo_bar`` line and an actual function header for ``i_foo_bar``
|
||||
with the same parameters. That’s a cumbersome job for more than 40 function headers. But Emacs
|
||||
comes to the rescue!
|
||||
|
||||
.. code-block:: lisp
|
||||
|
||||
(require 'thingatpt)
|
||||
|
||||
(defun get-point(symbol &optional arg)
|
||||
"Get point, optionally running a command beforehand"
|
||||
|
||||
(funcall symbol arg)
|
||||
(point))
|
||||
|
||||
(defun copy-symbol-at-point()
|
||||
"Copy the symbol under point"
|
||||
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(let ((beg (get-point 'beginning-of-thing 'symbol))
|
||||
(end (get-point 'end-of-thing 'symbol)))
|
||||
|
||||
(copy-region-as-kill beg end))))
|
||||
|
||||
(defun implement-gobject-vfunc()
|
||||
"Change a vtable line of a GObject interface to an implementation line like:
|
||||
void (*my_iface_func)(type1 param1, type2 param2, ...);
|
||||
to
|
||||
iface->my_iface_func = i_my_iface_func;"
|
||||
|
||||
(interactive)
|
||||
|
||||
(save-excursion
|
||||
(let ((beg ((lambda()
|
||||
(search-forward "(*")
|
||||
(point))))
|
||||
(end ((lambda()
|
||||
(back-to-indentation)
|
||||
(point)))))
|
||||
(kill-region beg end))
|
||||
(copy-symbol-at-point)
|
||||
(insert "iface->")
|
||||
(end-of-thing 'symbol)
|
||||
(delete-char 1)
|
||||
(let ((beg (point))
|
||||
(end ((lambda()
|
||||
(find-list-end)
|
||||
(point)))))
|
||||
(kill-region beg end))
|
||||
(insert " = i_")
|
||||
(yank 2))
|
||||
(next-line)
|
||||
(beginning-of-line))
|
||||
|
||||
(defun implement-gobject-vfunc-prototype()
|
||||
"Change a vtable line of a GObject interface to an implementation prototype line like:
|
||||
void (*my_iface_func)(type1 param1, type2 param2, ...);
|
||||
to
|
||||
static void
|
||||
i_my_iface_func(type1 param1, type2 param2, ...)"
|
||||
|
||||
(interactive)
|
||||
|
||||
(let ((beg ((lambda()
|
||||
(back-to-indentation)
|
||||
(point))))
|
||||
(end ((lambda()
|
||||
(beginning-of-line)
|
||||
(point)))))
|
||||
(kill-region beg end))
|
||||
(insert "static ")
|
||||
(search-forward "(*")
|
||||
(delete-char -3)
|
||||
(newline)
|
||||
(insert "i_")
|
||||
(end-of-thing 'symbol)
|
||||
(delete-char 1)
|
||||
(let ((beg (point))
|
||||
(end ((lambda()
|
||||
(find-list-end)
|
||||
(point)))))
|
||||
(indent-region beg end))
|
||||
(delete-char 1))
|
||||
|
||||
Now all I have to do is to copy the whole vtable entry into ``matrix_http_api_matrix_api_init()``,
|
||||
execute :kbd:`M-x implement-gobject-vfunc`, then put the same vtable entry somewhere before the
|
||||
interface init function, and execute :kbd:`M-x implement-gobject-vfunc-prototype`.
|
32
content/blog/2016-02-26-vala-interface-madness.rst
Normal file
32
content/blog/2016-02-26-vala-interface-madness.rst
Normal file
@@ -0,0 +1,32 @@
|
||||
Vala interface madness
|
||||
######################
|
||||
|
||||
:date: 2016-02-26T13:07:52Z
|
||||
:category: blog
|
||||
:tags: vala,development
|
||||
:url: 2016/02/26/vala-interface-madness/
|
||||
:save_as: 2016/02/26/vala-interface-madness/index.html
|
||||
:status: published
|
||||
:author: "Gergely Polonkai"
|
||||
|
||||
Although I have just started making it in C, I decided to move my Matrix GLib SDK to Vala. First
|
||||
to learn a new language, and second because it is much easier to write GObject based stuff with
|
||||
it.
|
||||
|
||||
For the first step I created a ``.vapi`` file from my existing sources, so the whole SDK prototype
|
||||
was available for me in Vala.
|
||||
|
||||
I had a ``MatrixEvent`` class that implemented the ``GInitable`` interface, and many others were
|
||||
subclassed ``MatrixEvent``. For some reason I don’t remember, I created the following header for
|
||||
one of the event classes:
|
||||
|
||||
.. code-block:: vala
|
||||
|
||||
public class MatrixPresenceEvent : GLib.Object, GLib.Initable {
|
||||
|
||||
This is nice and everything, but as I didn’t create an ``init()`` method for
|
||||
``MatrixPresenceEvent``, it tried to use the one from the parent class and somehow got into an
|
||||
infinite loop. The Vala transformer (``valac``), however, doesn’t mention this.
|
||||
|
||||
Lessons learned: if you implement an interface on a subclass that is implemented by the parent
|
||||
don’t forget to add the necessary functions to the subclass.
|
@@ -0,0 +1,38 @@
|
||||
Emacs package to generate GObject boilerplate
|
||||
#############################################
|
||||
|
||||
:date: 2016-09-28T15:40:15Z
|
||||
:category: blog
|
||||
:tags: gnome,development
|
||||
:url: 2016/09/28/emacs-package-to-generate-gobject-boilerplate/
|
||||
:save_as: 2016/09/28/emacs-package-to-generate-gobject-boilerplate/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Before I started using Vala (and sometimes even after that) I often needed to generate new classes
|
||||
based on `GObject <https://developer.gnome.org/gobject/stable/>`_.
|
||||
|
||||
If you have ever worked with GObject in C, you know how tedious it can be. You need a pretty long
|
||||
boilerplate just to register your class, and, if you want to be introspectable (and readable,
|
||||
actually), your function names can grow really long.
|
||||
|
||||
To overcome this problem back in my ViM days, I used template files, where I could replace class
|
||||
prefixes and names with a few keyboard macros. As I never really dug into ViM scripting other
|
||||
than using some plugins, I never got farther than that. `Then came Emacs
|
||||
<{filename}2014-09-17-nyanmacs.rst>`_.
|
||||
|
||||
I use Emacs for about two years now very extensively, up to and including GLib-based development.
|
||||
I tried the template approach, but it felt to be a really poor experience, especially given that I
|
||||
made my feet wet with Emacs Lisp. So I dug deeper, and created a package for that.
|
||||
|
||||
.. image:: {static}../images/screenshot-gobgen.png
|
||||
:alt: A screenshot of GobGen in action
|
||||
|
||||
GobGen has its own buffer with some widgets, a bit similar to ``customize``. You can enter the
|
||||
name of your new object and its parent, specify some settings. Then you press Generate, and you
|
||||
are presented with two new buffers, one for the ``.c`` and another for the ``.h`` boilerplate.
|
||||
|
||||
There are a lot of things to do, actually. There is already an open issue for creating a major
|
||||
mode for this buffer, and there are some minor switches I’d like to add, but it is already usable.
|
||||
You can grab it from `MELPA <https://melpa.org/#/gobgen>`_ (my first package there; woo!) or from
|
||||
my `GitHub account <https://github.com/gergelypolonkai/gobgen.el>`_.
|
63
content/blog/2016-10-04-git-merge-stages.rst
Normal file
63
content/blog/2016-10-04-git-merge-stages.rst
Normal file
@@ -0,0 +1,63 @@
|
||||
git-merge stages
|
||||
################
|
||||
|
||||
:date: 2016-10-04T12:46:00Z
|
||||
:category: blog
|
||||
:tags: git
|
||||
:url: 2016/10/04/git-merge-stages/
|
||||
:save_as: 2016/10/04/git-merge-stages/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
This was a mail to my company’s internal Git mailing list, after I realised many colleagues can’t
|
||||
wrap their heads around merge conflicts.
|
||||
|
||||
Hello all,
|
||||
|
||||
I just saw this on the `git-users <https://groups.google.com/forum/#!forum/git-users>`_ list
|
||||
and thought it could help you when you bump into a merge conflict. It is an excerpt from a
|
||||
mail by Konstantin Khomoutov (one of the main contributors on the list), with a few
|
||||
modifications of mine. Happy debugging :)
|
||||
|
||||
When a merge conflict is detected for a file, Git:
|
||||
|
||||
1. Updates the entry for that file in the index to make it contain
|
||||
several so-called “stages”:
|
||||
|
||||
* `0`: “Ours” version – that one which was there in this index entry
|
||||
before we begun to merge. At the beginning of the conflict, like
|
||||
right after the `git merge` or `git rebase` command this won’t
|
||||
exist (unless you had the file in the index, which you didn’t, did
|
||||
you?). When you resolve the conflict and use `git add
|
||||
my/conflicting/file.cc`, this will be the version added to the
|
||||
staging area (index), thus, the resolution of the conflict.
|
||||
* `1`: The version from the common ancestor commit, ie. the version
|
||||
of the file both of you modified.
|
||||
* `2`: The version from `HEAD`. During a merge, this is the current
|
||||
branch. During a rebase, this is the branch or commit you are
|
||||
rebasing onto, which usually will be `origin/develop`).
|
||||
* `3`: The version being merged, or the commit you are rebasing.
|
||||
2. Updates the file in the work tree to contain conflict markers and
|
||||
the conflicting chunks of text between them (and the text from the
|
||||
common ancestor if the `diff3` style of conflict markers was set).
|
||||
|
||||
Now you can use the numbers in point 1 to access the different stages
|
||||
of the conflicting file. For example, to see the common ancestor (the
|
||||
version both of you modified), use
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
git show :1:my/conflicting/file.cc
|
||||
|
||||
Or, to see the difference between the two conflicting versions, try
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
git diff :2:my/conflicting/file.cc :3:my/conflicting/file.cc
|
||||
|
||||
|
||||
**Note** that you can’t use the ``:0:`` stage *before* you stage your resolution with ``git
|
||||
add``, and you can’t use the ``:2:`` and ``:3:`` stages *after* you staged the resolution.
|
||||
|
||||
Fun fact: behind the scenes, these are the files (*revisions*) ``git mergetool`` accesses when
|
||||
it presents you the conflict visually.
|
76
content/blog/2016-11-03-how-i-started-with-emacs.rst
Normal file
76
content/blog/2016-11-03-how-i-started-with-emacs.rst
Normal file
@@ -0,0 +1,76 @@
|
||||
How I started with Emacs
|
||||
########################
|
||||
|
||||
:date: 2016-11-03T09:58:41Z
|
||||
:category: blog
|
||||
:tags: emacs
|
||||
:url: 2016/11/03/how-i-started-with-emacs/
|
||||
:save_as: 2016/11/03/how-i-started-with-emacs/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Sacha Chua has a nice `Emacs chat intro <http://sachachua.com/blog/2013/04/emacs-chat-intro/>`_
|
||||
article back from 2013. I write this post half because she asks there about my (OK, anyone’s)
|
||||
first Emacs moments, and half because I plan to do it for months now.
|
||||
|
||||
I wanted to start using Emacs 6(ish) years ago, and I was like “:kbd:`C-x` what”? (Note that back
|
||||
around 1998, I was among the people who exited ``vi`` by killing it from another terminal after a
|
||||
bunch of tries & fails like `these <http://osxdaily.com/2014/06/12/how-to-quit-vim/>`_.)
|
||||
|
||||
I tried to come back to Emacs a lot of times. And I mean a *lot*, about every two months. I
|
||||
suddenly learned what these cryptic key chord descriptions mean (``C`` is for :kbd:`Control` and
|
||||
``M`` is for :kbd:`Meta`, which is actually :kbd:`Alt`), but somehow it didn’t *click*. I
|
||||
remained a ViM power user with a huge pile of 3:sup:`rd` party plugins. Then `I found Nyan-macs
|
||||
<{filename}2014-09-17-nyanmacs.rst>`_), which converted me to Emacs, and it is final now. Many of
|
||||
my friends thought I’m just kidding this being the cause, but I’m not. I’m a huge fan of Nyan cat
|
||||
(did you know there is even a site called `nyan.cat <http://nyan.cat/>`_?) and since then I have
|
||||
it in my mode line:
|
||||
|
||||
.. image:: {static}../images/nyan-modeline.png
|
||||
:alt: Nyan modeline
|
||||
|
||||
…in my ``eshell`` prompt:
|
||||
|
||||
.. image:: {static}../images/nyan-eshell.png
|
||||
:alt: eshell prompt with a Nyan cat
|
||||
|
||||
…and I also `zone out <https://www.emacswiki.org/emacs/ZoneMode>`_ with Nyan cat:
|
||||
|
||||
.. image:: {static}../images/nyan-zone.png
|
||||
:alt: a text-based animation with Nyan cat
|
||||
|
||||
Now on to more serious stuff. After browsing through all the packages provided by `ELPA
|
||||
<http://elpa.gnu.org/>`_, I found tons of useful (and sometimes, less useful) packages, like `Helm
|
||||
<https://github.com/emacs-helm/helm/wiki>`_, `company <http://company-mode.github.io/>`_, `gtags
|
||||
<https://www.emacswiki.org/emacs/GnuGlobal>`_ (which introduced me to GNU Global, removing
|
||||
Exuberant ctags from my life), `magit <https://magit.vc/>`_, `Projectile
|
||||
<http://batsov.com/projectile/>`_, and `Org <http://orgmode.org/>`_ (OK, it’s actually part of
|
||||
Emacs for a while, but still). I still use these few, but in a month or two, I started to
|
||||
`version control <https://github.com/gergelypolonkai/my-emacs-d>`_ my ``.emacs.d`` directory, so I
|
||||
can easily transfer it between my home and work machine (and for a few weeks now, even to my
|
||||
phone: I’m using Termux on Android). Then, over these two years I wrote some packages like
|
||||
`GobGen <https://github.com/gergelypolonkai/gobgen.el>`_, and a small addon for Calendar providing
|
||||
`Hungarian holidays <https://github.com/gergelypolonkai/hungarian-holidays>`_, and I found a lot
|
||||
more (in no particular order):
|
||||
|
||||
* `git-gutter <https://github.com/syohex/emacs-git-gutter>`_
|
||||
* `multiple-cursors <https://github.com/magnars/multiple-cursors.el>`_
|
||||
* `origami <https://github.com/gregsexton/origami.el>`_
|
||||
* `ace-window <https://github.com/abo-abo/ace-window>`_
|
||||
* `avy <https://github.com/abo-abo/avy>`_
|
||||
* `beacon <https://github.com/Malabarba/beacon>`_
|
||||
|
||||
…and a lot more.
|
||||
|
||||
What is more important (to me) is that I started using the `use-package
|
||||
<https://github.com/jwiegley/use-package>`_ package, which can automatically download packages
|
||||
that are not installed on my current local system. Together with `auto-package-update
|
||||
<https://github.com/rranelli/auto-package-update.el>`_, it is *very* practical.
|
||||
|
||||
In addition, I started to follow the blogs of a bunch of Emacs users/gurus. I’ve already
|
||||
mentioned `Sacha Chua <http://sachachua.com/>`_. She’s a charming, cheerful person, writing a lot
|
||||
about Emacs and project management (among other things). Another one is `Bozhidar Batsov
|
||||
<http://batsov.com/>`_, who, among other things, had an initiate to lay down the foundation of a
|
||||
`common Elisp coding style <https://github.com/bbatsov/emacs-lisp-style-guide>`_. Another
|
||||
favourite of mine is `Endless Parentheses <http://endlessparentheses.com/>`_, whence I got a lot
|
||||
of ideas.
|
38
content/blog/2016-11-10-edit-file-as-other-user-in-emacs.rst
Normal file
38
content/blog/2016-11-10-edit-file-as-other-user-in-emacs.rst
Normal file
@@ -0,0 +1,38 @@
|
||||
Edit file as another user in Emacs
|
||||
##################################
|
||||
|
||||
:date: 2016-11-10T08:57:12Z
|
||||
:category: blog
|
||||
:tags: development,emacs
|
||||
:url: 2016/11/10/edit-file-as-other-user-in-emacs/
|
||||
:save_as: 2016/11/10/edit-file-as-other-user-in-emacs/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I have recently found `this article <http://emacsredux.com/blog/2013/04/21/edit-files-as-root/>`_
|
||||
by Bozhidar Batsov on opening the current file as root. I barely use `tramp
|
||||
<https://www.gnu.org/software/tramp/>`_ for sudo access, but when I do, I almost never use root as
|
||||
the target user. So I decided to fix it for my needs.
|
||||
|
||||
.. code-block:: lisp
|
||||
|
||||
(defun open-this-file-as-other-user (user)
|
||||
"Edit current file as USER, using `tramp' and `sudo'. If the current
|
||||
buffer is not visiting a file, prompt for a file name."
|
||||
(interactive "sEdit as user (default: root): ")
|
||||
(when (string= "" user)
|
||||
(setq user "root"))
|
||||
(let* ((filename (or buffer-file-name
|
||||
(read-file-name (format "Find file (as %s): "
|
||||
user))))
|
||||
(tramp-path (concat (format "/sudo:%s@localhost:" user) filename)))
|
||||
(if buffer-file-name
|
||||
(find-alternate-file tramp-path)
|
||||
(find-file tramp-path))))
|
||||
|
||||
If the user is not specified, the default is still root. Also, if the current buffer is not
|
||||
visiting a file, I prompt for a filename. As I’m not an ``ido`` user, I didn’t bother calling
|
||||
``ido-read-file-name``; `helm <https://github.com/emacs-helm/helm/wiki>`_ overrides
|
||||
``read-file-name`` for me anyway.
|
||||
|
||||
Unlike Bozhidar, I barely use this feature, so I didn’t bind this to a key.
|
46
content/blog/2016-11-18-get-passwords-from-id-manager.rst
Normal file
46
content/blog/2016-11-18-get-passwords-from-id-manager.rst
Normal file
@@ -0,0 +1,46 @@
|
||||
Get account data programatically from id-manager
|
||||
################################################
|
||||
|
||||
:date: 2016-11-18T12:43:13Z
|
||||
:category: blog
|
||||
:tags: emacs
|
||||
:url: 2016/11/18/get-passwords-from-id-manager/
|
||||
:save_as: 2016/11/18/get-passwords-from-id-manager/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I recently started using `id-manager <https://github.com/kiwanami/emacs-id-manager>`_. It is a
|
||||
nice little package that can store your passwords, encrypting them with GPG. My original reason
|
||||
was to store my GitHub access token for `github-notifier
|
||||
<https://github.com/xuchunyang/github-notifier.el>`_, but it soon turned out, it’s not *that*
|
||||
easy.
|
||||
|
||||
``id-manager`` is a nice package when it comes to storing your password and retrieving them for
|
||||
your own eyes. But it cannot retrieve account data programatically. Taking a look into its
|
||||
source code, I came up with this solution:
|
||||
|
||||
.. code-block:: lisp
|
||||
|
||||
(defun gpolonkai/idm-record-get-field (record field)
|
||||
"Get FIELD of an id-manager RECORD."
|
||||
(let ((funcname (intern (concat "idm-record-" (symbol-name field)))))
|
||||
(when (fboundp funcname)
|
||||
(funcall funcname record))))
|
||||
|
||||
(defun gpolonkai/idm-get-field-for-account (account field)
|
||||
"Get id-manager password for ACCOUNT."
|
||||
(let ((db (idm-load-db))
|
||||
(lookup-record nil))
|
||||
(dolist (record (funcall db 'get-all-records) password)
|
||||
(when (string= account (idm-record-name record))
|
||||
(setq lookup-record (gpolonkai/idm-record-get-field record field))))
|
||||
lookup-record))
|
||||
|
||||
(defmacro gpolonkai/idm-get-password-for-account (account)
|
||||
`(gpolonkai/idm-get-field-for-account ,account 'password))
|
||||
|
||||
(defmacro gpolonkai/idm-get-id-for-account (account)
|
||||
`(gpolonkai/idm-get-field-for-account ,account 'account-id))
|
||||
|
||||
I currently need only the account ID (ie. the username) and the password, but it’s pretty easy to
|
||||
add a macro to get the ``memo`` or ``update-time`` fields, too.
|
@@ -0,0 +1,80 @@
|
||||
Add Python docstring to the beginning of anything in Emacs
|
||||
##########################################################
|
||||
|
||||
:date: 2016-11-30T07:52:37Z
|
||||
:category: blog
|
||||
:tags: development,python,emacs
|
||||
:url: 2016/11/30/add-python-docstring-to-the-beginning-of-anything/
|
||||
:save_as: 2016/11/30/add-python-docstring-to-the-beginning-of-anything/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Now that I write Python code for a living, I write a lot of functions, classes, and modules. What
|
||||
I still tend to forget, and also find tedious, is adding docstrings. Unlike many developers,
|
||||
writing documentation is not an enemy of mine, but it usually comes to my mind when I finish
|
||||
implementation. The procedure, roughly, is this:
|
||||
|
||||
* Decide where I am (in a function, in a class but not in one of its methods, or not inside such a
|
||||
block at all)
|
||||
* Go to the beginning of the thing
|
||||
* Insert ``"""``
|
||||
* Leave a blank line
|
||||
* Insert ``"""``
|
||||
|
||||
One of my mottos is if something takes more than one step and you have to do it more than twice,
|
||||
you should automate it after the first time. This puts a small(ish) overhead on the second
|
||||
invocation (when you implement the automation), but it usually worth the time.
|
||||
|
||||
Since I use Emacs for pretty much everything coding-related (and many more, but that’s the topic
|
||||
of a different post), I wrote a small function to do it for me.
|
||||
|
||||
.. code-block:: lisp
|
||||
|
||||
(defun gpolonkai/prog-in-string-p ()
|
||||
"Return `t' if point is inside a string."
|
||||
(nth 3 (syntax-ppss)))
|
||||
|
||||
(defun gpolonkai/prog-in-comment-p ()
|
||||
"Return `t' if point is inside a comment."
|
||||
(nth 4 (syntax-ppss)))
|
||||
|
||||
(defun gpolonkai/python-add-docstring ()
|
||||
"Add a Python docstring to the current thing. If point is
|
||||
inside a function, add docstring to that. If point is in a
|
||||
class, add docstring to that. If neither, add docstring to the
|
||||
beginning of the file."
|
||||
(interactive)
|
||||
(save-restriction
|
||||
(widen)
|
||||
(beginning-of-defun)
|
||||
(if (not (looking-at-p "\\(def\\|class\\) "))
|
||||
(progn
|
||||
(goto-char (point-min))
|
||||
(back-to-indentation)
|
||||
(forward-char)
|
||||
(while (gpolonkai/prog-in-comment-p)
|
||||
(forward-line)
|
||||
(back-to-indentation)
|
||||
(forward-char)))
|
||||
(search-forward ":")
|
||||
(while (or (gpolonkai/prog-in-string-p)
|
||||
(gpolonkai/prog-in-comment-p))
|
||||
(search-forward ":")))
|
||||
(if (eq 1 (count-lines 1 (point)))
|
||||
(open-line-above)
|
||||
(open-line-below))
|
||||
(insert "\"\"\"")
|
||||
(open-line-below)
|
||||
(insert "\"\"\"")
|
||||
(open-line-above)))
|
||||
|
||||
There are still a lot of things to improve:
|
||||
|
||||
* it always inserts double quotes (althoug I couldn’t show a use-case when single quotes are
|
||||
preferred)
|
||||
* it doesn’t check for an existing docstring, just happily inserts a new one (leaving the old one
|
||||
alone, but generating a syntax error this way)
|
||||
* it would also be nice if I could jump to the beginning of a file even from a class method. I
|
||||
guess I will use prefix keys for that, but I’m not sure yet.
|
||||
|
||||
You can bet I will implement these features, so check back soon for an updated version!
|
40
content/blog/2016-12-08-slugify-in-python3.rst
Normal file
40
content/blog/2016-12-08-slugify-in-python3.rst
Normal file
@@ -0,0 +1,40 @@
|
||||
Slugify in Python 3
|
||||
###################
|
||||
|
||||
:date: 2016-12-08T12:54:19Z
|
||||
:category: blog
|
||||
:tags: development,python
|
||||
:url: 2016/12/08/slugify-in-python3/
|
||||
:save_as: 2016/12/08/slugify-in-python3/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
Today I needed a function to create a slug (an ASCII-only representation of a string). I went
|
||||
Googling a bit, and found an excellend `Flask snippet <http://flask.pocoo.org/snippets/5/>`_.
|
||||
Problem is, it is designed for Python 2, so I came up with a Python 3 version.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import re
|
||||
from unicodedata import normalize
|
||||
|
||||
_punctuation_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')
|
||||
|
||||
def slugify(text, delim='-'):
|
||||
"""
|
||||
Generate an ASCII-only slug.
|
||||
"""
|
||||
|
||||
result = []
|
||||
for word in _punctuation_re.split(text.lower()):
|
||||
word = normalize('NFKD', word) \
|
||||
.encode('ascii', 'ignore') \
|
||||
.decode('utf-8')
|
||||
|
||||
if word:
|
||||
result.append(word)
|
||||
|
||||
return delim.join(result)
|
||||
|
||||
As I don’t really like the transliteration done in the first example (e.g. converting ü to ue), I
|
||||
went with the second example.
|
@@ -0,0 +1,246 @@
|
||||
Finding non-translated strings in Python code
|
||||
#############################################
|
||||
|
||||
:date: 2016-12-22T09:35:11Z
|
||||
:category: blog
|
||||
:tags: development,python
|
||||
:url: 2016/12/22/finding-non-translated-strings-in-python-code/
|
||||
:save_as: 2016/12/22/finding-non-translated-strings-in-python-code/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
When creating multilingual software, be it on the web, mobile, or desktop, you will eventually
|
||||
fail to mark strings as translatable. I know, I know, we developers are superhuman and never do
|
||||
that, but somehow I stopped trusting myself recently, so I came up with an idea.
|
||||
|
||||
Right now I assist in the creation of a multilingual site/web application, where a small part of
|
||||
the strings come from the Python code instead of HTML templates. Call it bad practice if you
|
||||
like, but I could not find a better way yet.
|
||||
|
||||
As a start, I tried to parse the source files with simple regular expressions, so I could find
|
||||
anything between quotation marks or apostrophes. This attempt quickly failed with strings that
|
||||
had such characters inside, escaped or not; my regexps became so complex I lost all hope. Then
|
||||
the magic word “lexer” came to mind.
|
||||
|
||||
While searching for ready made Python lexers, I bumped into the awesome ``ast`` module. AST
|
||||
stands for Abstract Syntax Tree, and this module does that: parses a Python file and returns a
|
||||
tree of nodes. For walking through these nodes there is a ``NodeVisitor`` class (among other
|
||||
means), which is meant to be subclassed. You add a bunch of ``visitN`` methods (where ``N`` is an
|
||||
``ast`` class name like ``Str`` or ``Call``), instantiate it, and call its ``visit()`` method with
|
||||
the root node. For example, the ``visitStr()`` method will be invoked for every string it finds.
|
||||
|
||||
How does it work?
|
||||
=================
|
||||
|
||||
Before getting into the details, let’s me present you the code I made:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import ast
|
||||
import gettext
|
||||
from gettext import gettext as _
|
||||
import sys
|
||||
|
||||
|
||||
def get_func_name(node):
|
||||
cls = node.__class__.__name__
|
||||
|
||||
if cls == 'Call':
|
||||
return get_func_name(node.func)
|
||||
elif cls == 'Attribute':
|
||||
return '{}.{}'.format(
|
||||
get_func_name(node.value),
|
||||
node.attr)
|
||||
elif cls == 'Name':
|
||||
return get_func_name(node.id)
|
||||
elif cls == 'str':
|
||||
return node
|
||||
elif cls == 'Str':
|
||||
return "<String literal>"
|
||||
elif cls == 'Subscript':
|
||||
return '{}[{}]'.format(get_func_name(node.value),
|
||||
get_func_name(node.slice))
|
||||
elif cls == 'Index':
|
||||
return get_func_name(node.value)
|
||||
else:
|
||||
print('ERROR: Unknown class: {}'.format(cls))
|
||||
|
||||
|
||||
class ShowStrings(ast.NodeVisitor):
|
||||
TRANSLATION_FUNCTIONS = [
|
||||
'_', # gettext.gettext is often imported under this name
|
||||
'gettext',
|
||||
'gettext.gettext',
|
||||
# FIXME: this list is pretty much incomplete
|
||||
]
|
||||
UNTRANSLATED = 'untranslated 9'
|
||||
|
||||
def __init__(self, filename=None):
|
||||
super(ShowStrings, self).__init__()
|
||||
|
||||
self.in_call = []
|
||||
self.filename = filename or '<parsed string>'
|
||||
|
||||
def visit_with_trace(self, node, func):
|
||||
self.in_call.append((func, node.lineno, node.col_offset))
|
||||
self.visit(node)
|
||||
self.in_call.pop()
|
||||
|
||||
def visit_Str(self, node):
|
||||
# TODO: make it possible to ignore untranslated strings
|
||||
# TODO: make this ignore docstrings
|
||||
|
||||
# if we are not in a translator function, issue a warning
|
||||
if not self.in_call or \
|
||||
self.in_call[-1][0] not in self.TRANSLATION_FUNCTIONS:
|
||||
try:
|
||||
funcname = self.in_call[-1][0]
|
||||
except IndexError:
|
||||
funcname = None
|
||||
|
||||
funcall_msg = "outside a function call" if funcname is None \
|
||||
else "inside a call to {funcname}".format(
|
||||
funcname=funcname)
|
||||
|
||||
print("WARNING: Untranslated string found at "
|
||||
"{filename}:{line}:{col} {funcall_msg}".format(
|
||||
filename=self.filename,
|
||||
line=node.lineno,
|
||||
col=node.col_offset,
|
||||
funcall_msg=funcall_msg))
|
||||
|
||||
def visit_Call(self, node):
|
||||
# if we are in a translator function, issue a warninc
|
||||
if self.in_call and self.in_call[-1][0] in self.TRANSLATION_FUNCTIONS:
|
||||
print("WARNING: function call within a translation function at "
|
||||
"{filename}:{line}:{col}".format(filename=self.filename,
|
||||
line=node.lineno,
|
||||
col=node.col_offset))
|
||||
funcname = get_func_name(node)
|
||||
|
||||
for arg in node.args:
|
||||
self.visit_with_trace(arg, funcname)
|
||||
|
||||
for kwarg in node.keywords:
|
||||
self.visit_with_trace(kwarg.value, funcname)
|
||||
|
||||
def generic_visit(self, node):
|
||||
# if we are inside a translator function, issue a warning
|
||||
if self.in_call and self.in_call[-1][0] in self.TRANSLATION_FUNCTIONS:
|
||||
# Some ast nodes, like Add don’t have position information
|
||||
if hasattr(node, 'lineno'):
|
||||
print("WARNING: something not a string ({klass}) found in a "
|
||||
"translation function at {filename}:{line}:{col}".format(
|
||||
filename=self.filename,
|
||||
klass=node.__class__.__name__,
|
||||
line=node.lineno,
|
||||
col=node.col_offset))
|
||||
else:
|
||||
print("WARNING: something not a string ({klass}) found in a "
|
||||
"translation function. Position unknown; function call "
|
||||
"is at {filename}:{line}:{col}".format(
|
||||
filename=self.filename,
|
||||
klass=node.__class__.__name__,
|
||||
line=self.in_call[-1][1],
|
||||
col=self.in_call[-1][2]))
|
||||
|
||||
super(ShowStrings, self).generic_visit(node)
|
||||
|
||||
|
||||
def tst(*args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
def actual_tests():
|
||||
_('translated 1')
|
||||
tst(_('translated 2'))
|
||||
tst(gettext.gettext('translated 3'))
|
||||
tst(_('translated 4') + 'native 1')
|
||||
tst('native 2'
|
||||
'native 3')
|
||||
tst(_('native 4' + 'native 5'))
|
||||
tst('native 6', b='native 7')
|
||||
tst(_(tst('hello!')))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
filename = sys.argv[1]
|
||||
except IndexError:
|
||||
filename = __file__
|
||||
print("INFO: No filename specified, checking myself.")
|
||||
|
||||
with open(filename, 'r') as f:
|
||||
code = f.read()
|
||||
|
||||
root = ast.parse(code)
|
||||
|
||||
show_strings = ShowStrings(filename=filename)
|
||||
show_strings.visit(root)
|
||||
|
||||
The class initialization does two things: creates an empty ``in_call`` list (this will hold our
|
||||
primitive backtrace), and saves the filename, if provided.
|
||||
|
||||
``visitCall``, again, has two tasks. First, it checks if we are inside a translation function.
|
||||
If so, it reports the fact that we are translating something that is not a raw string. Although
|
||||
it is not necessarily a bad thing, I consider it bad practice as it may result in undefined
|
||||
behaviour.
|
||||
|
||||
Its second task is to walk through the positional and keyword arguments of the function call. For
|
||||
each argument it calls the ``visit_with_trace()`` method.
|
||||
|
||||
This method updates the ``in_call`` property with the current function name and the position of
|
||||
the call. This latter is needed because ``ast`` doesn’t store position information for every node
|
||||
(operators are a notable example). Then it simply visits the argument node, which is needed
|
||||
because ``NodeVisitor.visit()`` is not recursive. When the visit is done (which, with really
|
||||
deeply nested calls like ``visit(this(call(iff(you(dare)))))`` will be recursive), the current
|
||||
function name is removed from ``in_call``, so subsequent calls on the same level see the same
|
||||
“backtrace”.
|
||||
|
||||
The ``generic_visit()`` method is called for every node that doesn’t have a named visitor (like
|
||||
``visitCall`` or ``visitStr``. For the same reason we generate a warning in ``visitCall``, we do
|
||||
the same here. If there is anything but a raw string inside a translation function call,
|
||||
developers should know about it.
|
||||
|
||||
The last and I think the most important method is ``visitStr``. All it does is checking the last
|
||||
element of the ``in_call`` list, and generates a warning if a raw string is found somewhere that
|
||||
is not inside a translation function call.
|
||||
|
||||
For accurate reports, there is a ``get_func_name()`` function that takes an ``ast`` node as an
|
||||
argument. As function call can be anything from actual functions to object methods, this goes all
|
||||
down the node’s properties, and recursively reconstructs the name of the actual function.
|
||||
|
||||
Finally, there are some test functions in this code. ``tst`` and
|
||||
``actual_tests`` are there so if I run a self-check on this script, it will
|
||||
find these strings and report all the untranslated strings and all the
|
||||
potential problems like the string concatenation.
|
||||
|
||||
Drawbacks
|
||||
=========
|
||||
|
||||
There are several drawbacks here. First, translation function names are built in, to the
|
||||
``TRANSLATION_FUNCTIONS`` property of the ``ShowString`` class. You must change this if you use
|
||||
other translation functions like ``dngettext``, or if you use a translation library other than
|
||||
``gettext``.
|
||||
|
||||
Second, it cannot ignore untranslated strings right now. It would be great if a pragma like
|
||||
``flake8``’s ``# noqa`` or ``coverage.py``’s ``# pragma: no cover`` could be added. However,
|
||||
``ast`` doesn’t parse comment blocks, so this proves to be challenging.
|
||||
|
||||
Third, it reports docstrings as untranslated. Clearly, this is wrong, as docstrings generally
|
||||
don’t have to be translated. Ignoring them, again, is a nice challenge I couldn’t yet overcome.
|
||||
|
||||
The ``get_func_name()`` helper is everything but done. As long as I cannot remove that final
|
||||
``else`` clause, there may be error reports. If that happens, the reported class should be
|
||||
treated in a new ``elif`` branch.
|
||||
|
||||
Finally (and the most easily fixed), the warnings are simply printed on the console. It is nice,
|
||||
but it should be optional; the problems identified should be stored so the caller can obtain it as
|
||||
an array.
|
||||
|
||||
Bottom line
|
||||
===========
|
||||
|
||||
Finding strings in Python sources is not as hard as I imagined. It was fun to learn using the
|
||||
``ast`` module, and it does a great job. Once I can overcome the drawbacks above, this script
|
||||
will be a fantastic piece of code that can assist me in my future tasks.
|
@@ -0,0 +1,39 @@
|
||||
Rename automatically named foreign keys with Alembic
|
||||
####################################################
|
||||
|
||||
:date: 2017-01-02T09:41:23Z
|
||||
:category: blog
|
||||
:tags: mysql,development,flask,python
|
||||
:url: 2017/01/02/rename-automatically-named-foreign-keys-with-alembic/
|
||||
:save_as: 2017/01/02/rename-automatically-named-foreign-keys-with-alembic/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I have recently messed up my Alembic migrations while modifying my SQLAlchemy models. To start
|
||||
with, I didn’t update the auto-generated migration files to name the indexes/foreign keys a name,
|
||||
so Alembic used its own naming scheme. This is not an actual problem until you have to modify
|
||||
columns that have such constraints. I have since fixed this problem, but first I had to find
|
||||
which column references what (I had no indexes other than primary key back then, so I could go
|
||||
with foreign keys only). Here is a query I put together, mostly using `this article
|
||||
<http://www.binarytides.com/list-foreign-keys-in-mysql/>`_.
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT constraint_name,
|
||||
CONCAT(table_name, '.', column_name) AS 'foreign key',
|
||||
CONCAT(referenced_table_name, '.', referenced_column_name) AS 'references'
|
||||
FROM information_schema.key_column_usage
|
||||
WHERE referenced_table_name IS NOT NULL AND
|
||||
table_schema = 'my_app';
|
||||
|
||||
Now I could easily drop such constraints using
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
alembic.op.drop_constraint('users_ibfk1', 'users', type_='foreignkey')
|
||||
|
||||
and recreate them with
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
alembic.op.create_foreign_key('fk_user_client', 'users', 'clients', ['client_id'], ['id'])
|
@@ -0,0 +1,85 @@
|
||||
Category-based logging with Flask
|
||||
#################################
|
||||
|
||||
:date: 2017-03-26T22:00:52Z
|
||||
:category: blog
|
||||
:tags: development,python,flask
|
||||
:url: 2017/03/27/category-based-logging-with-flask/
|
||||
:save_as: 2017/03/27/category-based-logging-with-flask/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I’m in a team who are developing a Flask-based web application, which uses logging extensively.
|
||||
For a while now it spews out a lot of lines so the need arose to index them in ElasticSearch, and
|
||||
more importantly, to search through them for auditing purposes. This latter user story brought up
|
||||
one more question: why don’t we categorize our log messages? I quickly came up with an extended
|
||||
log format (``[auth]`` is the new category name):
|
||||
|
||||
.. code-block:: log
|
||||
|
||||
[2017-01-14 00:55:42,554] [8286] [INFO] [auth] invalid password for john@example.com [at __init__.py:12, in function utils.validate_login]
|
||||
|
||||
Here, ``[auth]`` is the category name. In the ideal solution, all I’d have to
|
||||
do is adding ``%(category)s`` to my formatter, and I could call
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.logger.info(message, extra={'auth': 'invalid password'})
|
||||
|
||||
to achieve this output. Unfortunately, ``Flask.logger`` (and, in the background, the ``logging``
|
||||
module) is not that easy to tame.
|
||||
|
||||
As it turns out, a Flask application’s ``logger`` property is a subclass of ``logging.Logger``, so
|
||||
my first idea was to monkey patch that class. When the app’s logger is initialised, it subclasses
|
||||
``logging.Logger`` and tweaks the log level so it goes down to ``logging.DEBUG`` if the app is
|
||||
running in debug mode. This is done by using a different logger class depending on the app
|
||||
config. Fortunately it doesn’t directly subclass ``logging.Logger``; it calls
|
||||
``logging.getLoggerClass()`` to find which class it should extend. To achieve my goals, all I had
|
||||
to do is to subclass the original logger class, and pass it to ``logging.setLoggerClass()``
|
||||
*before* I initialise my app, and I have a fail-safe(ish) solution. So far so good, on to the
|
||||
extra category parameter.
|
||||
|
||||
Now if you add a new variable to the formatter like my new ``%(category)s``, you get a nifty
|
||||
``KeyError`` saying there is no ``category`` in the format expansion dictionary. If you add
|
||||
``category='auth'`` to the ``app.logger.info()`` calls and its cousins, it’s fine, because these
|
||||
methods use the magic ``**kwarg`` argument to swallow it. Everything goes well until control
|
||||
arrives to the ``_log()`` method: it complains about that extra ``category`` keyword argument.
|
||||
Taking a peek at Python’s internals, I found two things: ``info()`, `error()``, and co. pass
|
||||
``*args`` and ``**kwargs`` to ``_log()`` unmodified, and the ``_log()`` method doesn’t have
|
||||
``**kwargs`` present in its definition to swallow it. A little doc reading later I found that if
|
||||
I want to pass extra arguments for such a formatter, I should do it via the ``extra`` keyword
|
||||
argument to ``_log()``. A call like ``app.logger.info('invalid password', extra={'category':
|
||||
'auth'})`` solved the problem. Now *that* is tedious.
|
||||
|
||||
My first idea was to override all the standard logging methods like ``info()`` and ``error()``,
|
||||
and handle ``category`` there. But this resulted in lots of repeating code. I changed the
|
||||
specification a bit, so my calls would look like ``info('message', category='auth)`` instead of
|
||||
the original plan of ``info('auth', 'message')``: as the logging methods pass all keyword
|
||||
arguments to ``_log()``, I can handle it there. So at the end, my new logger class only patches
|
||||
``_log()``, by picking out ``category`` from the kwarg list, and inserting it to ``extra`` before
|
||||
calling ``super``.
|
||||
|
||||
As you can see, this is a bit ugly solution. It requires me, the app
|
||||
author, to know about Flask’s internals (that I can set my own logging class
|
||||
before the app is created, and so the app will use it.) This means if Flask
|
||||
developers change the way how logging is done, I have to adapt and find a
|
||||
workaround for the new version (well, unless they let me directly set the
|
||||
logging class. That would make it easy.)
|
||||
|
||||
What is worse, I must know about Python internals. I know the ``extra`` kwarg is documented well
|
||||
(I just failed to notice), but this made adding a new formatter variable so hard. Python version
|
||||
doesn’t change as often as Flask version in this project, and I think the standard library won’t
|
||||
really change until 4.0, so I don’t think my tampering with a “protected” method will cause any
|
||||
trouble in the future. Still, this makes me feel a bit uneasy.
|
||||
|
||||
All the above can be circumvented if this class, and the whole solution have some tests. As my
|
||||
class uses the same method as Flask (use ``logging.getLoggerClass()`` as a base class instead of
|
||||
using ``logging.Logger()`` directly), if the base logger class changes in Python or in the running
|
||||
environment, my app won’t care. By checking if the app logger can use my special ``category``
|
||||
variable (ie. it doesn’t raise an exception *and* the category actually gets into the log output),
|
||||
I made sure my class is used as a base in Flask, so if they change the way they construct
|
||||
``app.logger``, I will know about it when I first run my tests after upgrading Flask.
|
||||
|
||||
If you are interested in such functionality (and more), you can grab it from `GitHub
|
||||
<https://github.com/gergelypolonkai/flask-logging-extras>`_, or via `PyPI
|
||||
<https://pypi.python.org/pypi/Flask-Logging-Extras/>`_.
|
@@ -0,0 +1,29 @@
|
||||
Add SysAdmin day to Emacs Calendar
|
||||
##################################
|
||||
|
||||
:date: 2017-10-02T09:37:52Z
|
||||
:category: blog
|
||||
:tags: emacs
|
||||
:url: 2017/10/02/add-sysadmin-day-to-emacs-calendar/
|
||||
:save_as: 2017/10/02/add-sysadmin-day-to-emacs-calendar/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I’m a SysAdmin since 1998. Maybe a bit earlier, if you count managing our home computer. This
|
||||
means `SysAdmin Day <http://sysadminday.com/>`_ is also celebrating me. However, my Emacs
|
||||
Calendar doesn’t show it for some reason.
|
||||
|
||||
The solution is pretty easy:
|
||||
|
||||
.. code-block:: lisp
|
||||
|
||||
(add-to-list 'holiday-other-holidays '(holiday-float 7 5 -1 "SysAdmin Day") t)
|
||||
|
||||
Now invoke :kbd:`M-x holidays-list` for any year, choosing “Other” as the category, and there you
|
||||
go:
|
||||
|
||||
.. code-block:: log
|
||||
|
||||
…
|
||||
Friday, July 28, 2017: SysAdmin Day
|
||||
…
|
54
content/blog/2018-06-26-please-welcome-calendar-social.rst
Normal file
54
content/blog/2018-06-26-please-welcome-calendar-social.rst
Normal file
@@ -0,0 +1,54 @@
|
||||
Please welcome Calendar.social
|
||||
##############################
|
||||
|
||||
:date: 2018-06-26T05:36:00Z
|
||||
:category: blog
|
||||
:tags: development
|
||||
:url: 2018/06/26/please-welcome-calendar-social/
|
||||
:save_as: 2018/06/26/please-welcome-calendar-social/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I started looking at decentralised/federated tools some years ago, but other than Matrix I didn’t
|
||||
use any of them until recently. Then this February I joined the Fediverse (federated universe) by
|
||||
spinning up my own `Mastodon <https://joinmastodon.org/>`_ instance. I’m not going to lie, this
|
||||
place is pure awesome. I follow only 53 people but unlike on Twitter or Facebook, I can’t just
|
||||
scroll through my timeline; I have to read them all. These 53 accounts are real persons over the
|
||||
Internet with meaningful posts. I could never find this on the noisy Twitter or Facebook timeline
|
||||
during the last 10 or so years.
|
||||
|
||||
Bragging aside, and given my strive for learning, I wanted to give back something to this
|
||||
community. I thought about an image sharing platform where people can share their photo albums
|
||||
with each other, but I realised I’m not that good at image handling. So I decided to make a
|
||||
calendar instead.
|
||||
|
||||
My app, creatively codenamed Calendar.social, aims to be a calendar service similar to Google
|
||||
Calendar (and, obviously, any calendar app you can find out there) but instead of using emails, it
|
||||
will use ActivityPub to share all the details (although I might add e-mail support sooner or
|
||||
later.)
|
||||
|
||||
I have a *lot* on my mind about what this tool should/could do when it’s done. In no particular
|
||||
order, here’s a list of them:
|
||||
|
||||
* events that can be private (only you and the (optional) guests see them), or public (anyone can
|
||||
see them). They will have all the usual fields like start/end time, location, and maybe an icon
|
||||
and a cover photo
|
||||
* multiple calendars you would expect from a calendar app. This way you can separate your
|
||||
birthday reminders from the dentist appointments
|
||||
* event sharing over ActivityPub and other channels (to be decided, but I think you can take email
|
||||
and maybe Matrix for granted.)
|
||||
* full calendar sharing. The other party can get access from a very basic free/busy level to full
|
||||
write access (which might be a good idea for family or company wide calendars.)
|
||||
* Holiday calendars that store national/bank holidays. Users can subscribe to them to see the
|
||||
holidays of a given country/area, and optionally set them as busy (on holiday weekdays) or free
|
||||
(on weekends that are actually workdays for some reason.)
|
||||
* Reminders! Because you obviously don’t want to forget the birthday of your significant other,
|
||||
your anniversary, or your barber appointment.
|
||||
* All this developed with time zones, localisation, and accessibility in mind.
|
||||
|
||||
That, and anything more that comes to my mind.
|
||||
|
||||
You can follow the development `here <https://gitea.polonkai.eu/gergely/calendar-social>`_. Also,
|
||||
feel free to ping me with your ideas on my `Mastodon account <https://social.polonkai.eu/@gergely>`_, `Matrix
|
||||
<https://matrix.to/#/@gergely:polonkai.eu>`_, or any other channels you can find under the
|
||||
“Contact me” menu.
|
37
content/blog/2018-07-19-recurring-events.rst
Normal file
37
content/blog/2018-07-19-recurring-events.rst
Normal file
@@ -0,0 +1,37 @@
|
||||
Recurring events are hard
|
||||
#########################
|
||||
|
||||
:date: 2018-07-19T13:22:00Z
|
||||
:category: blog
|
||||
:tags: development
|
||||
:url: 2018/07/19/recurring-events/
|
||||
:save_as: 2018/07/19/recurring-events/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
It was almost a month ago when I `announced
|
||||
<{filename}2018-06-26-please-welcome-calendar-social.rst>`_ the development of Calendar.social.
|
||||
Since then I’m over some interesting and some less interesting stuff; (web) development, after
|
||||
all, is just a recurrence of patterns. Speaking of recurrence, I arrived to a really interesting
|
||||
topic: recurring events.
|
||||
|
||||
My initial thought was like “oh, that’s easy! Let’s insert all future occurences as a separate
|
||||
``Event`` object, linking to the original one for the details. That makes handling exceptions
|
||||
easy, as I just have to update/delete that specific instance.” Well, not really. I mean, an
|
||||
event repeating daily *forever* would fill up the database quickly, isn’t it? That’s when I
|
||||
decided to look how other projects do it.
|
||||
|
||||
As it turns out, my first thought is about the same as everyone else has their mind, with about
|
||||
the same reasons. Then, they usually turn down the idea just like I did. And instead, they
|
||||
implement recurrence patterns and exception patterns.
|
||||
|
||||
My favourite is `this article
|
||||
<https://github.com/bmoeskau/Extensible/blob/master/recurrence-overview.md>`_ so far. The author
|
||||
suggests to use the recurrence patterns specced by `RFC2445
|
||||
<http://www.ietf.org/rfc/rfc2445.txt>`_ (the spec for the iCalendar format). The interesting part
|
||||
in this solution is how to query recurring events: you simply store the timestamp of the last
|
||||
occurence of the events (or, if the event repeats forever, the greatest timestamp your database
|
||||
supports.)
|
||||
|
||||
Choosing the maximum date seemed to be the tricky one, but it turned out both Python and popular
|
||||
SQL backends support dates up to the end of year 9999.
|
@@ -0,0 +1,220 @@
|
||||
Check if the last Git commit has test coverage
|
||||
##############################################
|
||||
|
||||
:date: 2018-07-26T12:49:52Z
|
||||
:category: blog
|
||||
:tags: python,development,testing
|
||||
:url: 2018/07/26/check-if-last-git-commit-has-test-coverage/
|
||||
:save_as: 2018/07/26/check-if-last-git-commit-has-test-coverage/index.html
|
||||
:status: published
|
||||
:author: Gergely Polonkai
|
||||
|
||||
I use Python at work and for private projects. I also aim to write tests for my code, especially
|
||||
recently. And as I usually don’t start from 100% code coverage (TDD is not my game), I at least
|
||||
want to know if the code I just wrote have full coverage.
|
||||
|
||||
The trick is to collect all the lines that changed, and all the lines that has no coverage. Then
|
||||
compare the two, and you have the uncovered lines that changed!
|
||||
|
||||
Getting the list of changed lines
|
||||
=================================
|
||||
|
||||
Recently, I bumped into
|
||||
`this article <https://adam.younglogic.com/2018/07/testing-patch-has-test/>`_. It is a great awk
|
||||
script that lists the lines that changed in the latest commit. I have really no problem with awk,
|
||||
but I’m pretty sure it can be done in Python, as that is my main language nowadays.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def get_changed_lines():
|
||||
"""Get the line numbers that changed in the last commit
|
||||
"""
|
||||
|
||||
git_output = subprocess.check_output('git show', shell=True).decode('utf-8')
|
||||
|
||||
current_file = None
|
||||
lines = {}
|
||||
left = 0
|
||||
right = 0
|
||||
|
||||
for line in git_output.split('\n'):
|
||||
match = re.match(r'^@@ -([0-9]+),[0-9]+ [+]([0-9]+),[0-9]+ @@', line)
|
||||
|
||||
if match:
|
||||
left = int(match.groups()[0])
|
||||
right = int(match.groups()[1])
|
||||
|
||||
continue
|
||||
|
||||
if re.match(r'^\+\+\+', line):
|
||||
current_file = line[6:]
|
||||
|
||||
continue
|
||||
|
||||
if re.match(r'^-', line):
|
||||
left += 1
|
||||
|
||||
continue
|
||||
|
||||
if re.match(r'^[+]', line):
|
||||
# Save this line number as changed
|
||||
lines.setdefault(current_file, [])
|
||||
lines[current_file].append(right)
|
||||
right += 1
|
||||
|
||||
continue
|
||||
|
||||
left += 1
|
||||
right += 1
|
||||
|
||||
return lines
|
||||
|
||||
OK, not as short as the awk script, but works just fine.
|
||||
|
||||
Getting the uncovered lines
|
||||
===========================
|
||||
|
||||
Coverage.py can list the uncovered lines with ``coverage report --show-missing``. For
|
||||
Calendar.social, this looks something like this:
|
||||
|
||||
.. code-block:: log
|
||||
|
||||
Name Stmts Miss Cover Missing
|
||||
----------------------------------------------------------------------
|
||||
calsocial/__init__.py 173 62 64% 44, 138-148, 200, 239-253, 261-280, 288-295, 308-309, 324-346, 354-363
|
||||
calsocial/__main__.py 3 3 0% 4-9
|
||||
calsocial/account.py 108 51 53% 85-97, 105-112, 125, 130-137, 148-160, 169-175, 184-200, 209-212, 221-234
|
||||
calsocial/app_state.py 10 0 100%
|
||||
calsocial/cache.py 73 11 85% 65-70, 98, 113, 124, 137, 156-159
|
||||
calsocial/calendar_system/__init__.py 10 3 70% 32, 41, 48
|
||||
calsocial/calendar_system/gregorian.py 77 0 100%
|
||||
calsocial/config_development.py 11 11 0% 4-17
|
||||
calsocial/config_testing.py 12 0 100%
|
||||
calsocial/forms.py 198 83 58% 49, 59, 90, 136-146, 153, 161-169, 188-195, 198-206, 209-212, 228-232, 238-244, 252-253, 263-267, 273-277, 317-336, 339-342, 352-354, 362-374, 401-413
|
||||
calsocial/models.py 372 92 75% 49-51, 103-106, 177, 180-188, 191-200, 203, 242-248, 257-268, 289, 307, 349, 352-359, 378, 392, 404-409, 416, 444, 447, 492-496, 503, 510, 516, 522, 525, 528, 535-537, 545-551, 572, 606-617, 620, 652, 655, 660, 700, 746-748, 762-767, 774-783, 899, 929, 932
|
||||
calsocial/security.py 15 3 80% 36, 56-58
|
||||
calsocial/utils.py 42 5 88% 45-48, 52-53
|
||||
----------------------------------------------------------------------
|
||||
TOTAL 1104 324 71%
|
||||
|
||||
All we have to do is converting these ranges into a list of numbers, and compare it with the
|
||||
result of the previous function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def get_uncovered_lines(changed_lines):
|
||||
"""Get the full list of lines that has not been covered by tests
|
||||
"""
|
||||
|
||||
column_widths = []
|
||||
uncovered_lines = {}
|
||||
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith('---'):
|
||||
continue
|
||||
|
||||
if line.startswith('Name '):
|
||||
match = re.match(r'^(Name +)(Stmts +)(Miss +)(Cover +)Missing$', line)
|
||||
assert match
|
||||
|
||||
column_widths = [len(col) for col in match.groups()]
|
||||
|
||||
continue
|
||||
|
||||
name = [
|
||||
line[sum(column_widths[0:idx]):sum(column_widths[0:idx]) + width].strip()
|
||||
for idx, width in enumerate(column_widths)][0]
|
||||
missing = line[sum(column_widths):].strip()
|
||||
|
||||
for value in missing.split(', '):
|
||||
if not value:
|
||||
continue
|
||||
|
||||
try:
|
||||
number = int(value)
|
||||
except ValueError:
|
||||
first, last = value.split('-')
|
||||
lines = range(int(first), int(last) + 1)
|
||||
else:
|
||||
lines = range(number, number + 1)
|
||||
|
||||
for lineno in lines:
|
||||
if name in changed_lines and lineno not in changed_lines[name]:
|
||||
uncovered_lines.setdefault(name, [])
|
||||
uncovered_lines[name].append(lineno)
|
||||
|
||||
return uncovered_lines
|
||||
|
||||
At the end we have a dictionary that has filenames as keys, and a list of changed but uncovered
|
||||
lines.
|
||||
|
||||
Converting back to ranges
|
||||
=========================
|
||||
|
||||
To make the final result more readable, let’s convert them back to a nice ``from_line-to_line``
|
||||
range list first:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def line_numbers_to_ranges():
|
||||
"""List the lines that has not been covered
|
||||
"""
|
||||
|
||||
changed_lines = get_changed_lines()
|
||||
uncovered_lines = get_uncovered_lines(changed_lines)
|
||||
|
||||
line_list = []
|
||||
|
||||
for filename, lines in uncovered_lines.items():
|
||||
lines = sorted(lines)
|
||||
last_value = None
|
||||
|
||||
ranges = []
|
||||
|
||||
for lineno in lines:
|
||||
if last_value and last_value + 1 == lineno:
|
||||
ranges[-1].append(lineno)
|
||||
else:
|
||||
ranges.append([lineno])
|
||||
|
||||
last_value = lineno
|
||||
|
||||
range_list = []
|
||||
|
||||
for range_ in ranges:
|
||||
first = range_.pop(0)
|
||||
|
||||
if range_:
|
||||
range_list.append(f'{first}-{range_[-1]}')
|
||||
else:
|
||||
range_list.append(str(first))
|
||||
|
||||
line_list.append((filename, ', '.join(range_list)))
|
||||
|
||||
return line_list
|
||||
|
||||
Printing the result
|
||||
===================
|
||||
|
||||
Now all that is left is to print the result on the screen in a format digestable by a human being:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def tabular_print(uncovered_lines):
|
||||
"""Print the list of uncovered lines on the screen in a tabbed format
|
||||
"""
|
||||
|
||||
max_filename_len = max(len(data[0]) for data in uncovered_lines)
|
||||
|
||||
for filename, lines in uncovered_lines:
|
||||
print(filename.ljust(max_filename_len + 2) + lines)
|
||||
|
||||
And we are done.
|
||||
|
||||
Conclusion
|
||||
==========
|
||||
|
||||
This task never seemed hard to accomplish, but somehow I never put enough energy into it to make
|
||||
it happen. Kudos to Adam Young doing some legwork for me!
|
Reference in New Issue
Block a user