Kirsle.net logo Kirsle.net

Tagged as: Python

Use Go as a Shell Scripting Language
November 29, 2016 by Noah

A very long time ago, I stumbled upon this article "Use Java for Everything". While I disagree that you should use Java for everything (or any programming language, for that matter), the author mentions that he wrote a wrapper script that lets him use Java for shell scripts (ones where you execute the Java source file directly, without the "write, compile, run" steps).

I wanted to do something similar for Go, because I had a very simple Go program I wanted to be able to throw into my .dotfiles repo and run without needing to do too many things first: a simple static HTTP server.

Read more...

Tags: 0 comments | Permalink
Let's Encrypt
December 30, 2015 by Noah

The free SSL certificate authority Let's Encrypt went into public beta earlier this month, and I updated all of my sites to use SSL now. I still had several more months before kirsle.net's old certificate from Namecheap expired, but I switched to the Let's Encrypt certificate because I could include all my subdomains instead of only the www one.

Check out their website and get free SSL certificates for your sites, too. I'm just writing this blog with some personal tips for how I configured my nginx server and a Python script I wrote to automate the process of renewing my certificates every month (Let's Encrypt certs expire every 90 days).

Read more...

Tags: 1 comment | Permalink
Event Loops
November 27, 2015 by Noah

This is something I wanted to rant about for a while: event loops in programming.

This post is specifically talking about programming languages that don't have their own built-in async model, and instead left it up to the community to create their own event loop modules instead. And how most of those modules don't get along well with the others.

Read more...

Tags: 0 comments | Permalink
Python/Tk Experiments
October 8, 2014 by Noah

I decided to take a look at the Tk GUI framework for Python and put together a simple mockup GUI to test various things.

I'd worked with Tk before in Perl (see my Perl CyanChat Client) and if you look at some of the Linux screenshots on that page, Tk looks ugly as hell in Perl.

Python's implementation of Tk (which they call Tkinter) is more modern than Perl's, and there's other neat helper modules like ttk which provides the Tk "Tile" theming engine which makes the standard Tk widgets look more modern, and takes a CSS-style approach to theming your widgets: instead of manually specifying things like background and foreground colors in each widget you program, you keep all that stuff in one central place and refer to it by name from the widgets.

For my Python mockup test app, I put together a rough copy of my Perl CyanChat Client GUI.

At first I was trying to use the ttk/Tile versions of the widgets (such as Button, Entry, etc.), but I ran into a rather annoying roadblock: in PCCC, my text Entry widgets have black background colors, and the insertion cursor (the little flashing I-beam in a text box) is also black by default. So when clicking in the text box, you wouldn't be able to see the insertion cursor.

In the standard Tkinter Entry widget, you can use the insertbackground option to change the color of the insertion cursor. But in ttk/Tile? There is no insertbackground option. Source - it's just not supported in ttk/Tile and their theming engine.

So I decided to not use ttk and just use the standard Tk widgets. I liked ttk's centralized styling system though, so I made a central class of dictionaries full of configuration attributes that I could easily reference when setting up my widgets. So, I eventually got my GUI put together and it looked nice enough I guess...

Tk widgets with ugly scrollbar

Except for those ugly scrollbars. The "1980s 3D" look to the scrollbar and those ugly triangle arrow widgets are from the Motif GUI which Tk was originally modeled to look like. It's ancient and it's ugly. This was also one of the main reasons why my Perl CyanChat Client looks so horrible under Linux, because this is Tk and Tk is ancient.

The Tile theming engine is supposed to fix this, but I wasn't using Tile in my code because of the aforementioned text insertion cursor problem. The best I could do with the standard Tk scrollbar is color it to make it look kind of "cool" at least, so I made it all black and grey to fit the theme of the rest of my GUI.

But then I figured out I can mix and match the widgets. I could import the Scrollbar from ttk while importing all the other widgets from Tkinter. The result?

Nice scrollbars!

That's better.

I probably won't create a full CyanChat client in Python because I really don't care about CyanChat much anymore, so this was mostly just me messing around with Tk and seeing how practical it is for certain use cases. But here's the source code anyway.

There's a few interesting things in the code, like I created my own "Scrolled" class for wrapping a widget in a scrollbar (works with Text and Listbox), so it's kinda like Python's ScrolledText module, but it's really more like Perl's Tk::Scrolled module in that it can wrap arbitrary widgets, not just Text.

Also, Tkinter's Text widget can't be made read-only. You can make a text box disabled, but that also prevents programmatic insertions/deletions as well. So I made a little function for inserting text that would first re-enable it, then insert text, then disable it again.

#!/usr/bin/env python

"""My test script for Python/Tk experimentation."""

import Tkinter as tk
from Tkinter import Tk, StringVar, Frame, Label, Text, Entry, Button, Listbox, END
from ttk import Scrollbar


class ChatClient(object):
    def __init__(self):
        # Styles
        self.style = MainWindowStyles()

        self.setup()

    def setup(self):
        self.mw = Tk()
        self.mw.title("Python CyanChat Client")
        resize_and_center(self.mw, 640, 480)

        # Variables
        self.nickname = StringVar(self.mw, "Kirsle")
        self.message = StringVar(self.mw, "--disabled--")

        # Top Frame (name entry box, buttons, conn status)
        self.login_frame = Frame(self.mw, **self.style.Frame)
        self.lower_frame = Frame(self.mw, **self.style.Frame)
        self.login_frame.pack(side="top", fill="x")
        self.lower_frame.pack(side="top", fill="both", expand=1)

        # The lower left (message entry, chat history) and lower right
        # (who lists)
        self.left_frame = Frame(self.lower_frame, **self.style.Frame)
        self.right_frame = Frame(self.lower_frame, **self.style.Frame)
        self.right_frame.pack(side="right", fill="y")
        self.left_frame.pack(side="right", fill="both", expand=1)

        # The message entry & chat history frames
        self.message_frame = Frame(self.left_frame, **self.style.Frame)
        self.dialogue_frame = Frame(self.left_frame, **self.style.Frame)
        self.message_frame.pack(side="top", fill="x")
        self.dialogue_frame.pack(side="top", fill="both", expand=1)

        ###
        # Top Frame Widgets
        ###

        self.name_label = Label(self.login_frame,
            text="Name:",
            **self.style.Label
        )
        self.name_entry = Entry(self.login_frame,
            textvariable=self.nickname,
            width=20,
            **self.style.DarkEntry
        )
        self.enter_exit_button = Button(self.login_frame,
            text="Enter chat",
            **self.style.Button
        )
        self.status_label = Label(self.login_frame,
            text="Connected to CyanChat",
            **self.style.ConnectedLabel
        )
        self.name_label.pack(side="left", padx=5, pady=5)
        self.name_entry.pack(side="left", pady=5)
        self.enter_exit_button.pack(side="left", padx=5, pady=5)
        self.status_label.pack(side="left")

        ###
        # Message Frame Widgets
        ###

        self.message_entry = Entry(self.message_frame,
            textvariable=self.message,
            state="disabled",
            **self.style.Entry
        )
        self.message_entry.pack(
            side="top",
            fill="x",
            padx=10,
            pady=10,
            expand=1,
        )

        ###
        # Who Frame Widgets
        ###

        self.who_label = Label(self.right_frame,
            text="Who is online:",
            anchor="w",
            **self.style.Label
        )
        self.who_label.pack(side="top", fill="x")

        self.who_list = Scrolled(self.right_frame, Listbox,
            attributes=self.style.Listbox,
            scrollbar=self.style.Scrollbar,
        )
        self.who_list.pack(side="top", fill="both", expand=1)

        for i in range(200):
            self.who_list.widget.insert(END, "Anonymous{}".format(i))

        ###
        # Dialogue Frame Widgets
        ###

        self.dialogue_text = Scrolled(self.dialogue_frame, Text,
            attributes=self.style.Dialogue,
            scrollbar=self.style.Scrollbar,
        )
        self.chat_styles(self.dialogue_text.widget)
        self.dialogue_text.pack(side="top", fill="both", padx=10, pady=0, expand=1)

        # Dummy junk
        messages = [
            [["[Kirsle]", "user"], [" Hello room!"]],
            [["\\\\\\\\\\", "server"], ["[Kirsle]", "user"], [" <links in from comcast.net Age>"], ["/////", "server"]],
            [["[ChatServer] ", "server"], ["Welcome to the Cyan Chat room."]],
            [["[ChatServer] ", "server"], ["There are only a few rules:"]],
            [["[ChatServer] ", "server"], ["   Be respectful and sensitive to others"]],
            [["[ChatServer] ", "server"], ["   And HAVE FUN!"]],
            [["[ChatServer] ", "server"], [""]],
            [["[ChatServer] ", "server"], ["Termination of use can happen without warning!"]],
            [["[ChatServer] ", "server"], [""]],
            [["[ChatServer] ", "server"], ["Server commands now available, type !\\? at the beginning of a line."]],
            [["[ChatServer] ", "server"], ["CyanChat Server version 2.12d"]],
        ]
        for i in range(80):
            messages.append([["[ChatClient]", "client"], [" Connecting..."]])
        messages.reverse()
        for line in messages:
            self.insert_readonly(self.dialogue_text, 0.0, "\n")
            line.reverse()
            for part in line:
                self.insert_readonly(self.dialogue_text, 0.0, *part)
        #self.insert_readonly(self.dialogue_text, END, "[Admin]", "admin")

    def chat_styles(self, widget):
        """Configure chat text styles."""
        # User colors
        widget.tag_configure("user", foreground="#FFFFFF")
        widget.tag_configure("guest", foreground="#FF9900")
        widget.tag_configure("admin", foreground="#00FFFF")
        widget.tag_configure("server", foreground="#00FF00")
        widget.tag_configure("client", foreground="#FF0000")

    def insert_readonly(self, widget, *args):
        """Insert text into a readonly (disabled) widget."""
        widget.widget.configure(state="normal")
        widget.widget.insert(*args)
        widget.widget.configure(state="disabled")

    def start(self):
        self.mw.mainloop()


class MainWindowStyles(object):
    """Simple Python class to hold style-related configurations for widgets."""
    Frame = dict(
        bg="#000000",
    )

    BaseLabel = dict(
        font="Verdana 8",
    )
    Label = dict(
        bg="#000000",
        fg="#CCCCCC",
        **BaseLabel
    )
    ConnectedLabel = dict(
        bg="#000000",
        fg="#00FF00",
        **BaseLabel
    )

    BaseFormCtrl=dict(
        highlightthickness=0, # Removes stupid border around the widget
    )

    BaseEntry = dict(
        insertwidth=1,
        selectborderwidth=0,
        selectbackground="#0099FF",
        font="Verdana 8",
        **BaseFormCtrl
    )
    Entry = dict(
        bg="#FFFFFF",
        fg="#000000",
        disabledbackground="#000000",
        disabledforeground="#666666",
        insertbackground="#000000",
        **BaseEntry
    )
    DarkEntry = dict(
        bg="#000000",
        fg="#CCCCCC",
        insertbackground="#FFFFFF", # Text insertion blinking cursor
        **BaseEntry
    )

    Listbox = dict(
        bg="#000000",
        fg="#CCCCCC",
        **BaseFormCtrl
    )

    Dialogue = dict(
        bg="#000000",
        fg="#CCCCCC",
        #disabledbackground="#000000",
        #disabledforeground="#CCCCCC",
        wrap=tk.WORD,
        state="disabled",
        **BaseEntry
    )

    Button = dict(
        bg="#000000",
        fg="#CCCCCC",
        activebackground="#000000",
        activeforeground="#0099FF",
        **BaseFormCtrl
    )

    # If using the Tkinter scrollbar, uncommon these. If using the ttk
    # scrollbar, use ttk's theming system instead.
    Scrollbar = dict(
        #relief="flat",
        #troughcolor="#000000",
        #bg="#606060",
        #activebackground="#999999",
        #borderwidth=1,
        #width=12,
        #highlightthickness=0,
    )


class Scrolled(object):
    """My own implementation for adding a scrollbar to a widget. Similar in
    principal to Python's ScrolledText module, but it works on other widgets too
    (this script uses it on Listbox too). So it's more like the Perl/Tk module
    Tk::Scrolled in that it can wrap any widget, in theory."""

    def __init__(self, master, widget_class, attributes=None, scrollbar=None):
        """
        master is the parent widget
        widget_class is the class, like Text or Listbox
        attributes are attributes for the widget
        scrollbar are attributes for the scrollbar
        """
        if attributes is None:
            attributes = []
        if scrollbar is None:
            scrollbar = []

        self.master = master

        # Parent frame to hold the widget + scrollbar
        self.frame  = Frame(master)

        # The scrollbar
        self.scrollbar = Scrollbar(self.frame, **scrollbar)

        # The widget itself
        self.widget = widget_class(self.frame,
            yscrollcommand=self.scrollbar.set,
            **attributes
        )
        self.scrollbar.configure(command=self.widget.yview)

        self.scrollbar.pack(side="right", fill="y")
        self.widget.pack(side="right", fill="both", expand=1)

    def widget(self):
        """Get at the inner widget."""
        return self.widget

    def scrollbar(self):
        """Get at the scrollbar widget."""
        return self.scrollbar

    def pack(self, **kwargs):
        """Wrapper so that pack() works as you'd expect."""
        self.frame.pack(**kwargs)


def resize_and_center(win, width, height):
    """Resize a window and center it on the screen."""
    screen_w = win.winfo_screenwidth()
    screen_h = win.winfo_screenheight()
    geometry = "{}x{}+{}+{}".format(
        width,
        height,
        screen_w / 2 - width / 2,
        screen_h / 2 - height / 2,
    )
    win.geometry(geometry)


if __name__ == "__main__":
    app = ChatClient()
    app.start()

Tags: 2 comments | Permalink
HTML5 Multi-File Uploader w/ Progress Bar
June 20, 2014 by Noah

The most recent feature I added to my website's CMS: multi-file uploads for the photo albums. I've been wanting to get around to this for a while so I can actually upload photo albums in bulk and make better use of that feature on my site. ;)

So I did some research and found some bits of example code here and there, and put together a pure HTML5 multiple-file uploader with progress bar. No Flash, no Java, no Internet Explorer 9 or lower. ;)

A lot of the existing bits of code I found out there weren't quite written in a way that was useful for my purposes. Their code tended to run the upload immediately after getting ahold of the files, i.e. they'd set up an HTML5 drag-and-drop spot and/or a multiple-file <input> box, and as soon as the user drops their pictures or selects them, the JavaScript would go right to work uploading them one by one to the back-end.

On my CMS I wanted to hold off on the uploading, because there's other form elements to take care of too, i.e. what album to put the pictures into or to apply a caption to them all. So I set up handlers for my file input box and drag-drop site to just put all the File objects into an array and wait for the submit button to be pushed.

So in my implementation, all the pictures are uploaded at once to the back-end, and there's only one progress bar (for the entire upload). It's possible to have one upload event per individual file, and therefore get progress bars on a file-by-file basis, but this didn't fit into my existing code structure.

Something I think is cool though is, on the back-end I'm using the exact same endpoint to handle uploads using Ajax (for those with JavaScript turned on) and when being POSTed to directly, i.e. for users with NoScript enabled. In both cases, they hit the /photos/upload on the server to send the form and images.

When the Ajax is the one doing it, it adds an extra __ajax form parameter. In this case, the back-end responds with a JSON response telling what the next URL is, and the JavaScript initiates a redirect to that URL. In case the user has JavaScript turned off, and the form POSTs to the back-end directly, the web server sends an HTTP redirect to the next URL.

Anyway, I threw together a quick Python/Flask app to mess with this stuff and figure it all out so I didn't have to worry about trying to wrangle existing code into doing something new. I have it hosted on Github here: https://github.com/kirsle/flask-multi-upload

The real interesting part is in the JavaScript source - only 184 lines of code, including comments and whitespace. Pretty straightforward. The same basic front-end code could be used regardless of the back-end, i.e. it could be uploading to a PHP script or something instead of a Python app. The Python part of the source is pretty short and sweet too.

Tags: 0 comments | Permalink
Markdown in Rophako
April 18, 2014 by Noah

I've made an update to my Rophako CMS that powers Kirsle.net: it now supports Markdown!

Blog posts can be either written in Markdown, or direct HTML (the old way). In Markdown mode, HTML is currently escaped from the post. I may change this and make it only escape HTML in comments if I find it to be a PITA in the future. ;) Oh, and emoticons still work (they're rendered after Markdown is done).

Markdown works in comments, too, but with some limitations, like I don't allow embedding images. Also, you can write entire pages on your site in Markdown. Just create a page named with a .md file extension that contains Markdown code and it "just works" (there's a built-in markdown.inc.html file in the default site that wraps your rendered document in your web design's layout).

Best of all, I'm using a handful of extensions to the Python Markdown module to enable a lot of Github style Markdown features, such as code highlighting. Here's a Python example:

def render_markdown(body, html_escape=True):
    """Render a block of Markdown text.

    This will default to escaping literal HTML characters. Set
    `html_escape=False` to trust HTML."""

    args = dict(
        lazy_ol=False, # If a numbered list starts at e.g. 4, show the <ol> there
        extensions=[
            "fenced_code",  # GitHub style code blocks
            "tables",       # http://michelf.ca/projects/php-markdown/extra/#table
            "smart_strong", # Handles double__underscore better.
            "codehilite",   # Code highlighting with Pygment!
            "nl2br",        # Line breaks inside a paragraph become <br>
            "sane_lists",   # Make lists less surprising
        ],
        extension_configs={
            "codehilite": {
                "linenums": False,
            }
        }
    )
    if html_escape:
        args["safe_mode"] = "escape"

    return markdown.markdown(body, **args)

I'll be revisiting all my old blog posts that have code pasted in them and reformatting them in Markdown for easier maintenance. My old way of pasting code in a blog post was to open the code in vim, and then run commands like,

:let html_use_css=1
:TOhtml

... which would output an HTML file that syntax highlights the code, and then I'd cut and paste all that junk into my blog. Blech. Markdown will be much cleaner. :D

Tags: 0 comments | Permalink
Goodbye, PerlSiikir!
April 6, 2014 by Noah
As of a few minutes ago, Kirsle.net is no longer powered by Perl. Instead, I've been working on a new content management system written in Python to replace it.

The reasons for the switch-over are numerous:

  1. The old Perl code was originally written for my previous version of my other project, Siikir, and the code was for an entire social networking type of site with lots of features, among which were Blogs, Photo Albums, and Comments (the three that Kirsle.net made use of). Kirsle.net didn't make use of the other features.
  2. The Perl code also had some memory leaks, which I tried for a while to eliminate but wasn't making much progress with. It was running as a FastCGI script, and the most notable side effects of the memory leaks were that my web server would randomly kill off unrelated processes, such as Minecraft servers or my XMPP server, because my index.cgi on Kirsle.net was sucking up so much memory. ;)
  3. Setting up PerlSiikir on a brand new server was an hours-long task. It needed a recent version of Perl, which needed a perlbrew installation done, and then a lot of modules needed manual installation. Seriously, look at my install notes. The new Python web app takes only minutes to set up.
  4. I like Python better nowadays than Perl. :)
And the best news of all is that my new Python CMS is open source!

I named the new project Rophako, because I was sitting at the Github "new repository" screen for a half hour trying to think of a name, and ended up just making use of my Azulian Translator to come up with a name. So, Rophako is Azulian for "Website." I'm a clever genius, I know. ;)

You can check out Rophako on Github: https://github.com/kirsle/rophako. The "default website" that comes with it isn't very polished yet; I literally just finished writing the code to support Kirsle.net. So, sometime later I'll tidy up the default website and have a working copy of it running on some subdomain like rophako.kirsle.net.

Anyway, this is the new CMS. I ported over all my old blog posts, comments, comment subscriptions, and things of the sort. All the old URLs should work too, due to my kirsle_legacy.py module in Rophako. If anybody finds any broken links or issues with the site, let me know. :)

Update (4/9/14):

I've polished up Rophako's default site and have an example running here: http://rophako.kirsle.net/

That's the site you'd get if you download and install Rophako (minus the blog posts and photos ;) ). So... the project is officially in "beta" status now and is usable!

Tags: 0 comments | Permalink
Minecraft Server Wrapper
February 13, 2014 by Noah
This is something I've been wanting to program for a while, and I finally have.

It's a Python app that wraps the Minecraft server and makes the server console available over a separate TCP socket (with password authentication, of course).

This allows you to telnet in to this TCP port, provide the password (or a challenge-response hashed version of it), and then you get access to the Minecraft server console. Anything the server outputs is broadcasted to all authenticated clients, and anything the clients send is sent to the Minecraft server. But the real strength in this isn't necessarily just being able to see and type commands into the server console (you can do this at the local shell running the server normally); it's for programs to do this automatically!

For example, you can have a whitelist of users who aren't operators, but you want them to be able to say for example !creative in the server chat window, and have their game mode switched to creative. This is one of the example scripts I included in my project, actually!

01-gamemode.py

And as a more eccentric example, I connected a simple RiveScript bot to this so that it can chat with players using the in-game chat system:

02-rivescript.py

You can get the source code and play around with this yourself at minecraft-control on GitHub!

Tags: 1 comment | Permalink