summaryrefslogtreecommitdiff
path: root/posts
diff options
context:
space:
mode:
Diffstat (limited to 'posts')
-rw-r--r--posts/2013-06-15-my-first-foss-contribution.skr39
-rw-r--r--posts/2013-06-30-find-me-on-diaspora.skr57
-rw-r--r--posts/2013-07-15-maine.skr34
-rw-r--r--posts/2013-07-20-stumpwm-on-debian-wheezy.skr62
-rw-r--r--posts/2013-08-07-guile-2d.skr48
-rw-r--r--posts/2013-08-08-angularjs-post-mortem.skr135
-rw-r--r--posts/2013-08-11-the-little-schemer.skr58
-rw-r--r--posts/2013-08-17-font-rendering-pango-cairo.skr328
-rw-r--r--posts/2013-09-22-liberating-a-thinkpad-x220.skr68
-rw-r--r--posts/2013-09-27-guile-2d-0.1.skr31
-rw-r--r--posts/2015-04-10-sxml-html-guile.skr235
-rw-r--r--posts/2015-08-30-ruby-on-guix.skr578
12 files changed, 1673 insertions, 0 deletions
diff --git a/posts/2013-06-15-my-first-foss-contribution.skr b/posts/2013-06-15-my-first-foss-contribution.skr
new file mode 100644
index 0000000..821bb02
--- /dev/null
+++ b/posts/2013-06-15-my-first-foss-contribution.skr
@@ -0,0 +1,39 @@
+(post
+ :title "My First Real FOSS Contribution"
+ :date (make-date* 2013 06 15)
+ :tags '("foss" "mediagoblin" "python" "federated" "decentralized" "wsu")
+ :summary "I added a small feature!"
+
+ (p [I spend a lot of my free time writing code. I usually work on my own
+personal projects that never really go anywhere. So, I decided to take
+a detour from my normal hacking routine and contribute to an existing
+free software project. My contribution was accepted awhile ago now,
+but I wasn’t blogging then so I’m rambling about it now.])
+
+ (p [It’s wise to find a project with a low barrier of entry. An
+active IRC channel and/or mailing list with people willing to help
+newcomers is ideal. I remembered hearing about
+,(anchor [GNU MediaGoblin] "http://mediagoblin.org")
+at LibrePlanet 2012, so I decided to check things out. MediaGoblin is
+a media sharing web application written in Python. Their bug tracker
+marks tickets that require little work and don’t require a deep
+understanding of MediaGoblin as “bitesized”.])
+
+ (p [I chose to work on
+,(anchor [this ticket] "http://issues.mediagoblin.org/ticket/453")
+because it didn’t require any complicated database migrations or
+knowledge of the media processing code. I added a new configuration
+option, ,(code [allow_comments]), and a small amount of code to enforce
+the setting.])
+
+ (p [Eventually, the ticket got reviewed and
+,(anchor [Christopher Webber] "http://dustycloud.org")
+(MediaGoblin’s friendly project leader) merged it: “Heya. Great
+branch, this works perfectly. Merged!”])
+
+ (p [It was a very small change, but I was happy to ,(em [finally])
+have some actual code of mine in a real free software project. I have
+a strong passion for free software and the GNU philosophy, so it’s
+really great to participate in the community. My job as a professional
+software developer eats up a lot of my time these days, but I hope to
+find the time to continue hacking and contributing.]))
diff --git a/posts/2013-06-30-find-me-on-diaspora.skr b/posts/2013-06-30-find-me-on-diaspora.skr
new file mode 100644
index 0000000..fc2410d
--- /dev/null
+++ b/posts/2013-06-30-find-me-on-diaspora.skr
@@ -0,0 +1,57 @@
+(post
+ :title "Find Me on Diaspora"
+ :date (make-date* 2013 06 30)
+ :tags '("foss" "diaspora" "federated" "decentralized" "rails" "wsu")
+ :summary "I have started using and contributing to Diaspora."
+
+ (p [With all of the recent news about the NSA’s widespread spying, I
+have decided to ween myself off of proprietary, centralized web
+services. Facebook, Google, and other such corporations hold onto
+massive amounts of our data that we’ve willingly given to them via
+status messages, “like” buttons, searches, and emails. Using and
+contributing to free (as in freedom), decentralized (like email) web
+services is a really great way to re-establish control of our data.
+These services rely on many small, interconnected nodes to operate,
+rather than a monolithic website that is under the control of one
+entity. If the distinction between centralized and decentralized
+isn’t clear, consider how email functions. There are many email
+providers to choose from. Users can communicate with others that
+choose to use a different email provider. This is how web services
+should work, but unfortunately very few work this way now.])
+
+ (p [The centralized web application that I spend too much time using
+is Facebook. I have knowingly given Facebook a “frontdoor” into my
+life for years now and I’m ready to move on. I think that the concept
+of a “social network” is fun, so I wanted a Facebook replacement.
+Fortunately, there is one:
+,(anchor [Diaspora] "http://diasporaproject.org/").])
+
+ (p [Diaspora is a
+,(anchor [free] "https://github.com/diaspora/diaspora"),
+distributed, social networking web application written in Ruby using
+the Rails framework. Diaspora is a community-run project. Its success
+depends upon users, developers, technical writers, user interface
+designers, etc. investing their time and/or money into making it
+better. The Diaspora network is broken up into many servers, known as
+,(anchor [pods] "http://podupti.me").
+Users have the choice of which pod to store their data on.
+Pods assert no ownership over their user’s data, unlike Facebook, and
+do not use that data for targeted advertisements. Diaspora is still a
+rather young project, but it does everything that I need it to
+do. Goodbye, Facebook!])
+
+ (p [Since I’m a programmer, I naturally wanted to hack on some code and
+contribute. The main developers are very friendly and give great
+guidance to newcomers that want to help out. Every Monday is a “Bug
+Mash Monday”, where a list of open issues is presented to entice
+contributors to resolve them. In the past few weeks, I have made two
+contributions to the Diaspora project: a
+,(anchor [bug fix] "https://github.com/diaspora/diaspora/issues/2948")
+and a
+,(anchor [small feature] "https://github.com/diaspora/diaspora/issues/2948").
+Diaspora is very hackable and I encourage other developers
+with Ruby/Rails and Javascript knowledge to join in.])
+
+ (p [TL\;R: Diaspora is great. Create an account. Check out my
+,(anchor [profile] "https://joindiaspora.com/u/davexunit").
+Start sharing. Happy hacking. :\)]))
diff --git a/posts/2013-07-15-maine.skr b/posts/2013-07-15-maine.skr
new file mode 100644
index 0000000..0c8e7e9
--- /dev/null
+++ b/posts/2013-07-15-maine.skr
@@ -0,0 +1,34 @@
+(post
+ :title "Maine!"
+ :date (make-date* 2013 07 15)
+ :tags '("maine" "vacation")
+ :summary "My vacation in Maine"
+
+ (p [Every summer, my friends and I go to Maine for a week. We stay in
+the Rockland area in an old house on a blueberry field. This year we
+hiked a mountain in Camden, hiked Mt. Katahdin, and went to the
+Rockland Blues Festival.])
+
+ (p [Here are some pictures taken from my not-so-great cellphone camera:])
+
+ (image/caption "/images/maine-2013/view-from-house.jpg"
+ [The view from the driveway of our house.])
+
+ (image/caption "/images/maine-2013/zekes-lookout.jpg"
+ [Sign at Zeke's Lookout. Somewhere on a mountain near Camden.])
+
+ (image/caption "/images/maine-2013/katahdin-camp.jpg"
+ [Mt. Katahdin as seen from the foot of the Hunt trail.])
+
+ (image/caption "/images/maine-2013/katahdin-waterfall.jpg"
+ [A beautiful waterfall that can be seen about a mile
+into the hike.])
+
+ (image/caption "/images/maine-2013/katahdin-view-1.jpg" [Katahdin])
+ (image/caption "/images/maine-2013/katahdin-view-2.jpg" [Katahdin again])
+
+ (image/caption "/images/maine-2013/katahdin-fog.jpg"
+ [Thick fog past the treeline.])
+
+ (image/caption "/images/maine-2013/blues-festival.jpg"
+ [Closed down street packed with people and bands in Rockland.]) )
diff --git a/posts/2013-07-20-stumpwm-on-debian-wheezy.skr b/posts/2013-07-20-stumpwm-on-debian-wheezy.skr
new file mode 100644
index 0000000..2268872
--- /dev/null
+++ b/posts/2013-07-20-stumpwm-on-debian-wheezy.skr
@@ -0,0 +1,62 @@
+(post
+ :title "StumpWM on Debian Wheezy"
+ :date (make-date* 2013 07 20)
+ :tags '("stumpwm" "common lisp" "debian" "wheezy" "wsu")
+ :summary "First steps with StumpWM on Debian Wheezy"
+
+ (p [Everyone that's ever talked to me about software development
+knows that I am in love with Emacs. Emacs has a wonderful keyboard
+driven interface and is almost infinitely customizable via Emacs Lisp.
+I've done a lot of programming in Emacs from my not-so-great laptop
+lately. My laptop has a rather poor 1280x800 resolution and low
+performing integrated graphics chip. Until today, I was running the
+GNOME 3 desktop environment on it. Unlike most people (or perhaps
+just a loud minority), I like GNOME 3. However, I wanted something
+that was both less graphics intensive and more keyboard driven than
+GNOME Shell and Mutter.])
+
+ (p [Someone on IRC told me about
+,(anchor [StumpWM] "http://www.nongnu.org/stumpwm/"),
+a window manager written entirely in Common Lisp. I had heard of
+StumpWM before, but back then I wasn't an Emacs user and I've never
+really stuck with any tiling window manager that I've tried (DWM,
+Awesome). Now that I know the power of a fully programmable
+environment thanks to Emacs, I decided to give StumpWM a try. After
+some initial pains trying to get it to run, I am now using it very
+happily.])
+
+ (p [Here is what I had to do to get StumpWM running on Debian Wheezy.])
+
+ (ol
+ (li [Install StumpWM] (source-code "sudo apt-get install stumpwm"))
+
+ (li [Create an ,(code [.xinitrc]) file in my home directory with the
+following text]
+ (source-code "exec stumpwm"))
+
+ (li (p [Workaround clisp "component not found" errors])
+ (p [I could not get StumpWM to start until I created the
+following symbolic links:])
+ (source-code
+ "ln -s /usr/share/common-lisp/source/stumpwm/stumpwm.asd \\
+ /usr/share/common-lisp/systems/stumpwm.asd
+ln -s /usr/share/common-lisp/source/cl-ppcre/cl-ppcre.asd \\
+ /usr/share/common-lisp/systems/cl-ppcre.asd"))
+
+ (li (p [Start the X server])
+ (source-code "startx")
+ (p [I use the GNOME Desktop Manager, so I also created a session
+file for StumpWM in ,(code [/usr/share/xsessions/stumpwm.desktop.])])
+ (source-code
+ "[Desktop Entry]
+Encoding=UTF-8
+Name=StumpWM
+Comment=This session logs you into StumpWM
+Exec=stumpwm
+TryExec=stumpwm
+Icon=
+Type=Application")))
+
+ (p [I hope this brief installation guide can be of use to one of you
+out there in Internet land. Perhaps in the future I will write an
+article about customizing StumpWM with Common Lisp.]))
diff --git a/posts/2013-08-07-guile-2d.skr b/posts/2013-08-07-guile-2d.skr
new file mode 100644
index 0000000..f612977
--- /dev/null
+++ b/posts/2013-08-07-guile-2d.skr
@@ -0,0 +1,48 @@
+(post
+ :title "guile-2d - A 2D Game Development Framework for GNU Guile"
+ :date (make-date* 2013 08 07)
+ :tags '("foss" "gnu" "guile" "scheme" "gamedev" "wsu")
+ :summary "I have started work on a 2D game framework"
+
+ (p [This is the very first devlog entry for my pet project,
+guile-2d. As the title suggests, guile-2d is a 2D game development
+framework for
+,(anchor [GNU Guile] "http://gnu.org/s/guile"),
+a Scheme implementation that has the honor of being the official
+extension language of the GNU project. Guile is a language with a
+growing number of features, but it still lacks a large assortment of
+libraries. I like to do 2D game programming, and I saw a niche that
+needed to be filled. Python has Pygame, Lua has Love, but there's no
+fun and accessible game programming library for Guile. Guile-2d is
+working to correct that.])
+
+ (p [The goal of Guile-2d is to create an easy to use 2D game
+programming framework. Libraries like SDL give the programmer a
+rather low-level set of tools that they can use to build a game,
+guile-2d will provide high-level tools over low-level SDL and OpenGL
+for commonly used elements of 2D games: tile maps, sprite animation,
+particle systems, collision detection, vector math, A* pathfinding,
+etc. Such features will allow a game developer to very quickly
+produce a working prototype with guile-2d.])
+
+ (p [Guile-2d is a framework, which means that it has some opinion
+about what the right way to do things is. The most apparent example
+of this is the game loop. The game loop runs at 60 frames-per-second
+and uses fixed timestep updates. Those that have read
+,(anchor [Fix Your Timestep]
+ "http://gafferongames.com/game-physics/fix-your-timestep/")
+will know that this decision is a good thing.])
+
+ (p [Perhaps the most important feature of guile-2d is the ability to
+do “live coding”. When the game loop starts, a REPL
+\(read-eval-print-loop\) server is started. Using the great
+,(anchor [Geiser] "http://geiser.nongnu.org/")
+extension for Emacs to connect to the REPL server, one can modify
+their game as it is running. This gives users the power to evaluate
+some new code and see the changes reflected immediately in the game
+window. No need to restart the game unless you crash it!])
+
+ (p [This has been a brief overview of some of the features and goals
+of guile-2d. If this project interests you, you can check out the
+source code on
+,(anchor [Github] "https://github.com/davexunit/guile-2d").]))
diff --git a/posts/2013-08-08-angularjs-post-mortem.skr b/posts/2013-08-08-angularjs-post-mortem.skr
new file mode 100644
index 0000000..2f7a804
--- /dev/null
+++ b/posts/2013-08-08-angularjs-post-mortem.skr
@@ -0,0 +1,135 @@
+(post
+ :title "AngularJS Post-mortem"
+ :date (make-date* 2013 08 08)
+ :tags '("web" "javascript" "angularjs" "wsu")
+ :summary "AngularJS likes/dislikes and what went right/wrong"
+
+ (p [,(anchor [AngularJS] "http://angularjs.org/")
+is the new popular client-side Javascript application framework
+developed by Google. We have recently adopted it at Vista Higher
+Learning for building our latest features that require a lot
+client-side logic. Now that I have a few applications under my belt,
+it’s time to talk about my experience.])
+
+ (p [If you want a quick TL\;DR: I think AngularJS is good, but it has
+a steep learning curve and there’s no well defined set of best
+practices.])
+
+ (p [Note: I will be using plenty of terms that will probably only
+make sense for people that have used AngularJS.])
+
+ (h2 [The Good Stuff])
+
+ (p [These are the things that went well. The things that made me glad
+that we chose to use AngularJS.])
+
+ (h3 [Easier Testing])
+
+ (p [Our Javascript test suite uses
+,(anchor [Jasmine.] "http://pivotal.github.io/jasmine/")
+AngularJS is built with test frameworks like Jasmine in mind.
+AngularJS could tends to be easily testable due to dependency
+injection. When the components of an application don’t rely on global
+state, it is easier to mock services for unit tests.])
+
+ (h3 [Separation of Concerns])
+
+ (p [AngularJS stresses separating the view from the data structures
+and logic behind it. I think everyone that’s written a somewhat
+complex JQuery application can relate to the mess of CSS selectors and
+click callbacks that the code quickly degenerates into.])
+
+ (p [AngularJS allows you to break up the DOM into logical chunks that
+are handled by separate controllers. Treating the application as many
+small pieces working together rather than one giant DOM blob keeps the
+code clean. Message passing via
+,(code [$emit]) and ,(code [$broadcast])
+keeps controllers loosely coupled to each other.])
+
+ (h3 [No More JQuery Spaghetti])
+
+ (p [Directives, the strategy for encapsulating DOM manipulation, are
+wonderful. It is an elegant solution to the mess that was JQuery
+selectors and event callbacks. AngularJS comes with a lot of
+directives out of the box to handle the most common stuff like
+showing/hiding elements, handling clicks, dynamically setting CSS
+classes.])
+
+ (h3 [More Maintainable Code])
+
+ (p [AngularJS is feature-rich. It does a lot on your behalf, which
+greatly reduces the amount of boilerplate code needed to get a
+prototype up and running. I had the opportunity to essentially
+rewrite an existing JQuery application using AngularJS. The results
+were clear: The AngularJS version had fewer lines of code, was more
+readable, and was easier to debug.])
+
+ (h2 [Bumps in the Road])
+
+ (p [These are the things that didn’t go smoothly. They boil down to
+AngularJS having a steep learning curve and ill-informed software
+design decisions on my part.])
+
+ (h3 [Understanding the Magic])
+
+ (p [A lot of things seem to happen by magic. For example, it is
+possible to make a asynchronous request and get a promise object in
+return. When the promise is assigned to a variable on ,(code [$scope]),
+AngularJS not only knows to ignore it while the request hasn’t
+finished, but it will re-assign to that variable the value of the
+asynchronous call when it completes. It is a nice feature, but it
+takes some digging to find out what is really going on.])
+
+ (h3 [Poor Documentation])
+
+ (p [I know I’m not the only one that hates the official AngularJS
+documentation. The documentation is getting more complete, but it’s
+still not very useful. Functions frequently have a blurb describing
+what they do, but no explanation of the parameter list. It’s hard to
+use a function that doesn’t describe what it expects for input.])
+
+ (p [When the documentation confused us, which it did frequently, we
+turned to the AngularJS book from
+,(anchor [O’Reilly] "http://shop.oreilly.com/product/0636920028055.do")
+for help. I need to get around to reading more of it.])
+
+ (h3 [RESTful Resources and Rails])
+
+ (p [AngularJS claims to be targeted at CRUD applications, but using
+the HTTP backend and the ,(code [Resource]) abstraction that sits on
+top of it was particularly difficult. A good amount of time was spent
+on trying to get the HTTP requests from resources to conform to what
+our Rails backend expects, like root-wrapping.])
+
+ (h3 [Bloated Controllers])
+
+ (p [I frequently made controllers that had too much state and logic
+that should have been extracted into services/factories/etc. A
+controller would start out slim but would quickly grow to several
+hundred lines of code as features were added. Controllers should be
+the middle man between the view and the model and that’s it.])
+
+ (p [Some tell-tale signs that a controller is becoming bloated:])
+
+ (ul
+ (li [There are a lot of private functions (not defined on
+,(code [$scope]))])
+ (li [Functions are defined on ,(code [$scope]) just so you can
+unit-test them, but are never used in the template]))
+
+ (p [I attribute controller bloat to a lack of knowing the appropriate uses
+for other AngularJS components. It was easy to just keep adding to the
+controller.])
+
+ (h2 [Conclusion])
+
+ (p [Overall, I think things went well, but I (and the rest of my
+team) made a lot of beginner mistakes. But that’s the learning
+process, isn’t it?])
+
+ (p [Now that I know more about AngularJS, it will be easier to make
+better design decisions moving forward.])
+
+ (p [I believe that as AngularJS continues to mature, some concensus
+in the community about best practices will emerge that will make
+things easier for beginners.]))
diff --git a/posts/2013-08-11-the-little-schemer.skr b/posts/2013-08-11-the-little-schemer.skr
new file mode 100644
index 0000000..d4e4211
--- /dev/null
+++ b/posts/2013-08-11-the-little-schemer.skr
@@ -0,0 +1,58 @@
+(post
+ :title "The Little Schemer"
+ :date (make-date* 2013 08 11)
+ :tags '("scheme" "books" "wsu")
+ :summary "I bought “The Little Schemer”"
+
+ (p [Yesterday, I took a trip to the MIT Press Bookstore and picked up
+a copy of
+,(anchor [The Little Schemer] "http://mitpress.mit.edu/books/little-schemer").
+I’ve only spent a few hours reading and coding along with it, but I’ve
+had a lot of fun. The following is a mini-review based on my
+experience thus far.])
+
+ (p [“The Little Schemer” teaches you to think recursively using an
+interesting and comedic writing style and the Scheme programming
+language. While Scheme is the language of choice, the real goal is to
+teach you problem solving rather than the details of a specific
+language. The book starts off simple, explaining what atoms, lists,
+and s-expressions are. Rather than providing the definition and then
+showing examples, it first gives examples in the form of a question
+and answer.])
+
+ (p [Example:])
+
+ (blockquote
+ (p [Is it true that this an atom?])
+ (p (strong [atom]))
+ (p [Yes, because ,(strong [atom]) is a string of characters
+beginning with a letter.]))
+
+ (p [From the examples given, a definition is created. In later
+examples, a Scheme procedure is written that produces the correct
+answers for all of the questions stated before it. It’s fun to build
+the procedure, verify that it works for all cases, and compare your
+implementation with the book’s.])
+
+ (p [“The Little Schemer” defines ten commandments that are essential
+to correctly solving the problems in the book. Some commandments are
+first given in an incomplete form, and expanded later when a greater
+level of understanding has been achieved. The problems that you solve
+reinforce the commandments. You might notice that you start writing
+procedures without thinking much about it, much like the muscle memory
+earned from using Emacs a lot. Gerald J. Sussman was right when he
+said that this book “can perform the same service that Hanon’s finger
+exercises or Czerny’s piano studies perform for the student of the
+piano.” I have no idea who Hanon and Czerny are, but I get it. For
+the drummers out there, you could liken this book to
+,(anchor [Stick Control]
+ "http://www.amazon.com/Stick-Control-For-Snare-Drummer/dp/1892764040").])
+
+ (p [The writing style is very informal, comedic, and food themed.
+Page 13 has a space reserved for jelly stains, and page 52 tells you
+to “go cons a piece of cake onto your mouth.” I have laughed a number
+of times while reading. Oh, and let’s not forget about the cute
+elephant drawings. This is definitely not your average boring, dry
+computer science book. If you are interested in a unique and
+enjoyable learning experience, then I highly recommend reading “The
+Little Schemer”.]))
diff --git a/posts/2013-08-17-font-rendering-pango-cairo.skr b/posts/2013-08-17-font-rendering-pango-cairo.skr
new file mode 100644
index 0000000..59e0f96
--- /dev/null
+++ b/posts/2013-08-17-font-rendering-pango-cairo.skr
@@ -0,0 +1,328 @@
+(define guile-2d-url "https://github.com/davexunit/guile-2d")
+(define madsy9-url "http://www.reddit.com/user/Madsy9")
+(define madsy9-comment-url
+ "http://www.reddit.com/r/scheme/comments/1k739l/guile_2d_game_programming_lib_for_scheme/cbmnyuk")
+(define pango-url "http://www.pango.org/")
+(define cairo-url "http://cairographics.org/")
+;; TODO: Move to files.dthompson.us
+(define tarball-url "/src/pangocairo.tar.gz")
+
+(post
+ :title "Font Rendering in OpenGL with Pango and Cairo"
+ :date (make-date* 2013 08 17)
+ :tags '("opengl" "pango" "cairo" "font" "wsu")
+ :summary "A brief tutorial for rendering fonts in OpenGL with libpangocairo"
+
+ (p [I am working towards a 0.1 release of my game development
+ framework for GNU Guile, ,(anchor "guile-2d" guile-2d-url). One of
+ the few remaining blockers on my to-do list is font rendering. A
+ reddit user, ,(anchor "Madsy9" madsy9-url), pointed me in the right
+ direction with this ,(anchor "comment" madsy9-comment-url). There are
+ two libraries needed to perform nice font rendering with proper
+ internationalization support: ,(anchor "Pango" pango-url), “a library
+ for laying out and rendering of text, with an emphasis on
+ internationalization,” and ,(anchor "Cairo" cairo-url), “a 2D graphics
+ library with support for multiple output devices.”])
+
+ (p [It took me awhile to put together all of the pieces and build a
+ working sample program. The goal of this post is to help others that
+ may be trying to accomplish a similar task that have no prior
+ knowledge of Pango and Cairo. I will assume basic knowledge of C,
+ SDL, and OpenGL throughout this post.])
+
+ (p [Let's get the basic SDL and OpenGL initialization out of the way:])
+
+ (source-code
+ (c-source
+ "#include <pango/pangocairo.h>
+#include <SDL.h>
+#include <SDL_opengl.h>
+
+#define WINDOW_WIDTH 800
+#define WINDOW_HEIGHT 600
+#define FONT \"Sans Bold 18\"
+#define TEXT \"The quick brown fox is so かわいい!\"
+
+void
+init_sdl ()
+{
+ SDL_Init (SDL_INIT_EVERYTHING);
+ SDL_SetVideoMode (WINDOW_WIDTH, WINDOW_HEIGHT, 0, SDL_OPENGL);
+}
+
+void
+init_gl ()
+{
+ glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
+ glDisable (GL_DEPTH_TEST);
+ glEnable (GL_BLEND);
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable (GL_TEXTURE_2D);
+ glViewport (0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
+ glMatrixMode (GL_PROJECTION);
+ glLoadIdentity ();
+ glOrtho (0, WINDOW_WIDTH, WINDOW_HEIGHT, 0, -1, 1);
+ glMatrixMode (GL_MODELVIEW);
+ glLoadIdentity ();
+}"))
+
+ (p [,(code "create_texture") simply creates an OpenGL texture given an array of
+ pixel data and the texture dimensions. Our Cairo surface will use BGRA
+ color.])
+
+ (source-code
+ (c-source
+ "unsigned int
+create_texture (unsigned int width,
+ unsigned int height,
+ unsigned char *pixels)
+{
+ unsigned int texture_id;
+
+ glGenTextures (1, &texture_id);
+ glBindTexture (GL_TEXTURE_2D, texture_id);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexImage2D (GL_TEXTURE_2D,
+ 0,
+ GL_RGBA,
+ width,
+ height,
+ 0,
+ GL_BGRA,
+ GL_UNSIGNED_BYTE,
+ pixels);
+
+ return texture_id;
+}"))
+
+ (p [,(code "draw_texture") clears the screen, renders a simple textured quad
+ using OpenGL’s immediate mode, and then swaps buffers.])
+
+ (source-code
+ (c-source
+ "void
+draw_texture (int width,
+ int height,
+ unsigned int texture_id)
+{
+ /* Render a texture in immediate mode. */
+ glMatrixMode (GL_MODELVIEW);
+ glLoadIdentity ();
+ glClear (GL_COLOR_BUFFER_BIT);
+ glPushMatrix ();
+ glBindTexture (GL_TEXTURE_2D, texture_id);
+ glColor3f (1.f, 1.0f, 1.0f);
+
+ glBegin (GL_QUADS);
+ glTexCoord2f (0.0f, 0.0f);
+ glVertex2f (0.0f, 0.0f);
+ glTexCoord2f (1.0f, 0.0f);
+ glVertex2f (width, 0.0f);
+ glTexCoord2f (1.0f, 1.0f);
+ glVertex2f (width , height);
+ glTexCoord2f (0.0f, 1.0f);
+ glVertex2f (0.0f, height);
+ glEnd ();
+
+ glPopMatrix ();
+ SDL_GL_SwapBuffers();
+}"))
+
+ (p [,(code [create_cairo_context]) is used to make a new Cairo context that
+draws to a raw data surface. The return value, a ,(code [cairo_t]), is the
+main object in Cairo. All drawing is done via a ,(code [cairo_t]) object. A
+context needs a surface to draw on.
+,(code [cairo_image_surface_create_for_data]) creates a raw data surface for
+us. We will be translating the surface into a texture later on.])
+
+ (source-code
+ (c-source
+ "cairo_t*
+create_cairo_context (int width,
+ int height,
+ int channels,
+ cairo_surface_t** surf,
+ unsigned char** buffer)
+{
+ *buffer = calloc (channels * width * height, sizeof (unsigned char));
+ *surf = cairo_image_surface_create_for_data (*buffer,
+ CAIRO_FORMAT_ARGB32,
+ width,
+ height,
+ channels * width);
+ return cairo_create (*surf);
+}"))
+
+ (p [,(code [create_layout_context]) also makes a new Cairo context, but this
+context is for PangoLayout objects. In Pango, a layout describes the
+style of a paragraph of text. The layout needs a context in order to
+function. We use ,(code [cairo_image_surface_create]) with dimensions of 0x0
+because we won’t actually be rendering to this surface. Instead, we
+will layout our text and use ,(code [create_cairo_context]) to build a
+context with a surface that is the size of the rendered text. Cairo
+uses reference counting for dynamically allocated objects, so we need
+to call ,(code [cairo_surface_destroy]) when we’re done with the temporary
+surface. The context still maintains a reference to the surface, so
+the memory for the surface will not be freed until the context is.])
+
+ (source-code
+ (c-source
+ "cairo_t*
+create_layout_context ()
+{
+ cairo_surface_t *temp_surface;
+ cairo_t *context;
+
+ temp_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0);
+ context = cairo_create (temp_surface);
+ cairo_surface_destroy (temp_surface);
+
+ return context;
+}"))
+
+ (p [,(code [get_text_size]) tells us the size of the text that’s in the layout,
+in pixels. Pango’s units are not in pixels, so we must divide by
+,(code [PANGO_SCALE]) in order to get pixel units.])
+
+ (source-code
+ (c-source
+ "void
+get_text_size (PangoLayout *layout,
+ unsigned int *width,
+ unsigned int *height)
+{
+ pango_layout_get_size (layout, width, height);
+ /* Divide by pango scale to get dimensions in pixels. */
+ *width /= PANGO_SCALE;
+ *height /= PANGO_SCALE;
+}"))
+
+ (p [,(code [render_text]) is where all of the magic happens. First, we create a
+layout with a layout context and set the text that we will render with
+this layout. ,(code [TEXT]) is defined earlier in the program as “The quick
+brown fox is so かわいい!”])
+
+ (p [Then we create a ,(code [PangoFontDescription]) object. This object
+represents the font that we want to render. Earlier in the program,
+,(code [FONT]) is defined as “Sans Bold 18”. Pango is able to figure out
+how to load a font from a string in this format. Your system must be
+able to recognize the font family and font face, though. I haven’t
+yet figured out how to have Pango render an arbitrary font from a
+,(code [*.ttf]) file.])
+
+ (p [Next, we create a rendering context by getting the layout’s size and
+creating a context with a surface big enough to show all of the
+rendered text.])
+
+ (p [Finally, we set the font color to white, render the text to the
+surface with ,(code [pango_cairo_show_layout]), and create an OpenGL texture
+from the surface. We also clean up all the objects that we no longer
+need before returning.])
+
+ (source-code
+ (c-source
+ "unsigned int
+render_text (const char *text,
+ unsigned int *text_width,
+ unsigned int *text_height,
+ unsigned int *texture_id)
+{
+ cairo_t *layout_context;
+ cairo_t *render_context;
+ cairo_surface_t *temp_surface;
+ cairo_surface_t *surface;
+ unsigned char* surface_data = NULL;
+ PangoFontDescription *desc;
+ PangoLayout *layout;
+
+ layout_context = create_layout_context ();
+
+ /* Create a PangoLayout, set the font and text */
+ layout = pango_cairo_create_layout (layout_context);
+ pango_layout_set_text (layout, text, -1);
+
+ /* Load the font */
+ desc = pango_font_description_from_string (FONT);
+ pango_layout_set_font_description (layout, desc);
+ pango_font_description_free (desc);
+
+ /* Get text dimensions and create a context to render to */
+ get_text_size (layout, text_width, text_height);
+ render_context = create_cairo_context (*text_width,
+ *text_height,
+ 4,
+ &surface,
+ &surface_data);
+
+ /* Render */
+ cairo_set_source_rgba (render_context, 1, 1, 1, 1);
+ pango_cairo_show_layout (render_context, layout);
+ *texture_id = create_texture(*text_width, *text_height, surface_data);
+
+ /* Clean up */
+ free (surface_data);
+ g_object_unref (layout);
+ cairo_destroy (layout_context);
+ cairo_destroy (render_context);
+ cairo_surface_destroy (surface);
+}"))
+
+ (p [,(code [main]) is pretty simple. We initialize SDL and OpenGL, render text
+to a texture, and enter the rendering loop. The program will run
+until you click the close button, press “enter”, or press “q”.])
+
+ (source-code
+ (c-source
+ "int main (int argc, char **argv)
+{
+ SDL_Event event;
+ int keep_running = 1;
+ unsigned int texture_id;
+ unsigned int text_width = 0;
+ unsigned int text_height = 0;
+
+ init_sdl ();
+ init_gl ();
+ render_text(TEXT,
+ &texture_id,
+ &text_width,
+ &text_height);
+
+ /* Update/render loop */
+ while (keep_running) {
+ SDL_PollEvent (&event);
+
+ switch (event.type) {
+ case SDL_QUIT :
+ keep_running = 0;
+ break;
+
+ case SDL_KEYDOWN :
+ if (event.key.keysym.sym == SDLK_ESCAPE)
+ keep_running = 0;
+ if (event.key.keysym.sym == SDLK_q)
+ keep_running = 0;
+ break;
+ }
+
+ draw_texture (texture_id, text_width, text_height);
+ SDL_Delay (16);
+ }
+
+ /* Clean up */
+ glDeleteTextures (1, &texture_id);
+
+ SDL_Quit();
+
+ return 0;
+}"))
+
+ (p [And we’re done! You should now be able to render some text in an
+OpenGL context. I hope this brief tutorial was helpful. Font rendering
+isn’t easy, and it’s not really my area of interest. I’m glad that
+Pango exists to do all of the real work for me so that I can more
+quickly move on to the parts of graphics programming that I actually
+enjoy.])
+
+ (p [You can download the full source code ,(anchor [here] tarball-url).]))
diff --git a/posts/2013-09-22-liberating-a-thinkpad-x220.skr b/posts/2013-09-22-liberating-a-thinkpad-x220.skr
new file mode 100644
index 0000000..a527482
--- /dev/null
+++ b/posts/2013-09-22-liberating-a-thinkpad-x220.skr
@@ -0,0 +1,68 @@
+(post
+ :title "Liberating a Thinkpad X220"
+ :date (make-date* 2013 09 22)
+ :tags '("thinkpad" "free software" "wsu")
+ :summary "I bought a used Thinkpad X220 and made it more free
+software friendly."
+
+ (p [I had been looking for a suitable replacement to my old, slow
+Compaq laptop that I purchased during my freshman year of college when
+I had very little money. What I liked about my old laptop was that it
+played well with free software. I had no trouble getting all of my
+hardware to work out-of-the-box with fully free GNU/Linux
+distributions such as Trisquel, and I wanted any future laptops of
+mine to play nicely, too.])
+
+ (p [I have heard much praise for Thinkpads over the years. Solid
+build quality, utilitarian design, and cheap to buy used. However,
+upon further reading, I realized that most newer Thinkpads require
+nonfree software in order to the drive the Intel wireless chip.
+Furthermore, there was DRM present in the BIOS that would prevent the
+installation of PCIe wireless chips that weren't in the whitelist.])
+
+ (p [This really bummed me out, but I bought a Thinkpad anyway. I
+found a great deal on a used X220 on ebay for $400. In order to
+liberate it, I had to make a small deal with the devil: Use the
+pre-installed Windows 7 to flash a hacked BIOS that removes the
+whitelist. I could only find the needed BIOS as a Windows executable,
+so I didn't have much choice. This process left me hoping that
+coreboot gains wider adoption.])
+
+ (p [Once I had verified that I didn't brick my Thinkpad, I installed
+the new wireless card. I purchased a Wireless N, half-height, mini
+PCIe card from
+,(anchor [Thinkpenguin]
+ "https://www.thinkpenguin.com/gnu-linux/penguin-wireless-n-half-height-mini-pcie-card").
+It uses an Atheros chipset and is free software compatible. I met
+Chris, the owner of Thinkpenguin, at this year's Northeast GNU/Linux
+Fest at Harvard. He is doing some great work and I wanted to support
+his business. It was nice to buy from someone who could assure me
+that the hardware I purchased is fully supported on a libre GNU/Linux
+distribution.])
+
+ (p [Now that my Thinkpad was free (sans BIOS, of course), it was time
+for the final touch. I replaced the hard drive with a 128GB SSD and
+installed Debian testing. It takes roughly 9 seconds to get from GRUB
+to the GDM login screen. It feels very nice to have a device that
+boots so quickly.])
+
+ (p [Now that everything had been installed and configured, I was able
+to start hacking and get a feel for things. The keyboard is the
+nicest I've ever used on a laptop. The
+,(anchor [TrackPoint] "http://xkcd.com/243/")
+is quite a nice way to move around once you get used to it. The
+ThinkLight is pretty neat when you're in a dark area. The battery
+life is extremely impressive. I don't know exactly how long it lasts
+yet but I never have to charge it while I am using it. I was lucky if
+I got 2 hours of battery life out of my old laptop, which caused me to
+be constantly tethered to an AC adapter. The screen is matte, which
+is awesome because it's very difficult to use a laptop outdoors when
+the screen is glossy. 1366x768 is not an ideal resolution (16:9 be
+damned), but I can live with it on a 12.5\" screen. Last but not
+least, I honestly like the aesthetics. A lot of people are enamored
+with the brushed aluminum designs by that fruit company, but I love
+the flat black, functional design of the Thinkpad.])
+
+ (p [I hope to really break this thing in over the weekend at the
+,(anchor [GNU 30th Anniversary] "https://www.gnu.org/gnu30/")
+hackathon. :\)]))
diff --git a/posts/2013-09-27-guile-2d-0.1.skr b/posts/2013-09-27-guile-2d-0.1.skr
new file mode 100644
index 0000000..0a5f01f
--- /dev/null
+++ b/posts/2013-09-27-guile-2d-0.1.skr
@@ -0,0 +1,31 @@
+(post
+ :title "Guile-2D 0.1 Released"
+ :date (make-date* 2013 09 27)
+ :tags '("foss" "gnu" "guile" "scheme" "gamedev" "wsu")
+ :summary "Official release of Guile-2D Version 0.1"
+
+ (p [To celebrate the GNU Project's 30th anniversary, I have decided
+to make the very first release of my 2D game development framework for
+,(anchor [GNU Guile] "http://gnu.org/software/guile").
+GNU Guile is a Scheme implementation, and has the honor of being the
+official extension language of the GNU project. Guile-2D is a layer
+above SDL, OpenGL, FreeImage, and FTGL that provides abstractions for
+common 2D game programming requirements such as sprites, tilesets,
+animations, scripting, and collision detection.])
+
+ (p [There is a lot of work to do in order to get Guile-2D up to snuff
+with the game libraries for more popular languages like Python and
+Lua. I am looking for contributors who share my vision of creating a
+fully featured, easy to use game library in Scheme.])
+
+ (p [Guile-2D currently supports GNU/Linux distributions. I am
+looking for help to get it running on OS X and Windows.])
+
+ (p [Please refer to the ,(code [INSTALL.org]), ,(code [README.org]),
+and texinfo files to learn how to install Guile-2D, run example
+programs, and write your own games.])
+
+ ;; TODO: Move to files.dthompson.us
+ (p (anchor [Download the release tarball] "/src/guile-2d-0.1.tar.gz"))
+ (p (anchor [Browse the source code on GitHub]
+ "https://github.com/davexunit/guile-2d")))
diff --git a/posts/2015-04-10-sxml-html-guile.skr b/posts/2015-04-10-sxml-html-guile.skr
new file mode 100644
index 0000000..beea865
--- /dev/null
+++ b/posts/2015-04-10-sxml-html-guile.skr
@@ -0,0 +1,235 @@
+(post
+ :title "Rendering HTML with SXML and GNU Guile"
+ :date (make-date* 2015 04 10)
+ :tags '("gnu" "guile" "wsu")
+ :summary "With a little effort, SXML can be used for HTML templates"
+
+ (p [GNU Guile provides modules for working with XML documents called
+ SXML. SXML provides an elegant way of writing XML documents as
+ s-expressions that can be easily manipulated in Scheme. Here’s an
+ example:])
+
+ (source-code
+ (scheme-source
+ "(sxml->xml '(foo (bar (@ (attr \"something\")))))"))
+
+ (source-code
+ (xml-source
+ "<foo><bar attr=\"something\" /></foo>"))
+
+ (p [I don’t know about you, but I work with HTML documents much more
+ often than XML. Since HTML is very similar to XML, we should be able
+ to represent it with SXML, too!])
+
+ (source-code
+ (scheme-source
+ "(sxml->xml
+ '(html
+ (head
+ (title \"Hello, world!\")
+ (script (@ (src \"foo.js\"))))
+ (body
+ (h1 \"Hello!\"))))"))
+
+ (source-code
+ (xml-source
+ "<html>
+ <head>
+ <title>Hello, world!</title>
+ <script src=\"foo.js\" /> <!-- what? -->
+ </head>
+ <body>
+ <h1>Hello!</h1>
+ </body>
+</html>"))
+
+ (p [That ,(code [<script>]) tag doesn’t look right! Script tags
+ don’t close themselves like that. Well, we could hack around it:])
+
+ (source-code
+ (scheme-source
+ "(sxml->xml
+ '(html
+ (head
+ (title \"Hello, world!\")
+ (script (@ (src \"foo.js\")) \"\"))
+ (body
+ (h1 \"Hello!\"))))"))
+
+ (source-code
+ (xml-source
+ "<html>
+ <head>
+ <title>Hello, world!</title>
+ <script src=\"foo.js\"></script>
+ </head>
+ <body>
+ <h1>Hello!</h1>
+ </body>
+</html>"))
+
+ (p [There’s no bug. The improper rendering happens because HTML, while
+ similar to XML, has some different syntax rules. Instead of using
+ ,(code [sxml->xml]) a new procedure that is tailored to the HTML syntax
+ is needed. Introducing ,(code [sxml->html]):])
+
+ (source-code
+ (scheme-source
+ "(define* (sxml->html tree #:optional (port (current-output-port)))
+ \"Write the serialized HTML form of TREE to PORT.\"
+ (match tree
+ (() *unspecified*)
+ (('doctype type)
+ (doctype->html type port))
+ (((? symbol? tag) ('@ attrs ...) body ...)
+ (element->html tag attrs body port))
+ (((? symbol? tag) body ...)
+ (element->html tag '() body port))
+ ((nodes ...)
+ (for-each (cut sxml->html <> port) nodes))
+ ((? string? text)
+ (string->escaped-html text port))
+ ;; Render arbitrary Scheme objects, too.
+ (obj (object->escaped-html obj port))))"))
+
+ (p [In addition to being aware of void elements and escape
+characters, it can also render ,(code ['(doctype "html")]) as
+,(code [<!DOCTYPE html>]). If we replace ,(code [sxml->xml])
+with ,(code [sxml->html]) in the failing example above we can see
+that it does the right thing.])
+
+ (source-code
+ (scheme-source
+ "(sxml->html
+ '((script (@ (src \"foo.js\")))
+ \"Copyright © 2015 David Thompson <davet@gnu.org>\"))"))
+
+ (source-code
+ (xml-source
+ "<script src=\"foo.js\"></script>
+Copyright © 2015 David Thompson &lt;davet@gnu.org&gt;"))
+
+ (p [Here’s the full version of my ,(code "(sxml html)") module. It’s
+quite brief, which is a nice bonus. This code requires Guile 2.0.11
+or greater.])
+
+ (p [Happy hacking!])
+
+ (source-code
+ (scheme-source
+ ";;; Copyright © 2015 David Thompson <davet@gnu.org>
+;;;
+;;; This library is free software; you can redistribute it and/or
+;;; modify it under the terms of the GNU Lesser General Public License
+;;; as published by the Free Software Foundation; either version 3 of
+;;; the License, or (at your option) any later version.
+;;;
+;;; This library 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
+;;; Lesser General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU Lesser General Public
+;;; License along with this library. If not, see
+;;; <http://www.gnu.org/licenses/>.
+
+(define-module (sxml html)
+ #:use-module (sxml simple)
+ #:use-module (srfi srfi-26)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 format)
+ #:use-module (ice-9 hash-table)
+ #:export (sxml->html))
+
+(define %void-elements
+ '(area
+ base
+ br
+ col
+ command
+ embed
+ hr
+ img
+ input
+ keygen
+ link
+ meta
+ param
+ source
+ track
+ wbr))
+
+(define (void-element? tag)
+ \"Return #t if TAG is a void element.\"
+ (pair? (memq tag %void-elements)))
+
+(define %escape-chars
+ (alist->hash-table
+ '((#\\\" . \"quot\")
+ (#\\& . \"amp\")
+ (#\\' . \"apos\")
+ (#\\< . \"lt\")
+ (#\\> . \"gt\"))))
+
+(define (string->escaped-html s port)
+ \"Write the HTML escaped form of S to PORT.\"
+ (define (escape c)
+ (let ((escaped (hash-ref %escape-chars c)))
+ (if escaped
+ (format port \"&~a;\" escaped)
+ (display c port))))
+ (string-for-each escape s))
+
+(define (object->escaped-html obj port)
+ \"Write the HTML escaped form of OBJ to PORT.\"
+ (string->escaped-html
+ (call-with-output-string (cut display obj <>))
+ port))
+
+(define (attribute-value->html value port)
+ \"Write the HTML escaped form of VALUE to PORT.\"
+ (if (string? value)
+ (string->escaped-html value port)
+ (object->escaped-html value port)))
+
+(define (attribute->html attr value port)
+ \"Write ATTR and VALUE to PORT.\"
+ (format port \"~a=\\\"\" attr)
+ (attribute-value->html value port)
+ (display #\\\" port))
+
+(define (element->html tag attrs body port)
+ \"Write the HTML TAG to PORT, where TAG has the attributes in the
+list ATTRS and the child nodes in BODY.\"
+ (format port \"<~a\" tag)
+ (for-each (match-lambda
+ ((attr value)
+ (display #\\space port)
+ (attribute->html attr value port)))
+ attrs)
+ (if (and (null? body) (void-element? tag))
+ (display \" />\" port)
+ (begin
+ (display #\\> port)
+ (for-each (cut sxml->html <> port) body)
+ (format port \"</~a>\" tag))))
+
+(define (doctype->html doctype port)
+ (format port \"<!DOCTYPE ~a>\" doctype))
+
+(define* (sxml->html tree #:optional (port (current-output-port)))
+ \"Write the serialized HTML form of TREE to PORT.\"
+ (match tree
+ (() *unspecified*)
+ (('doctype type)
+ (doctype->html type port))
+ (((? symbol? tag) ('@ attrs ...) body ...)
+ (element->html tag attrs body port))
+ (((? symbol? tag) body ...)
+ (element->html tag '() body port))
+ ((nodes ...)
+ (for-each (cut sxml->html <> port) nodes))
+ ((? string? text)
+ (string->escaped-html text port))
+ ;; Render arbitrary Scheme objects, too.
+ (obj (object->escaped-html obj port))))")))
diff --git a/posts/2015-08-30-ruby-on-guix.skr b/posts/2015-08-30-ruby-on-guix.skr
new file mode 100644
index 0000000..5da4ab4
--- /dev/null
+++ b/posts/2015-08-30-ruby-on-guix.skr
@@ -0,0 +1,578 @@
+(post
+ :title "Ruby on Guix"
+ :date (make-date* 2015 08 30)
+ :tags '("gnu" "guix" "scheme" "guile" "ruby" "wsu")
+ :summary "How to use Guix + some elbow grease to replace RVM and
+Bundler on GNU/Linux"
+
+ (p [I’ve been working with Ruby professionally for over 3 years now
+and I’ve grown frustrated with two of its most popular development
+tools: RVM and Bundler. For those that may not know, RVM is the Ruby
+version manager and it allows unprivileged users to download, compile,
+install, and manage many versions of Ruby instead of being stuck with
+the one that is installed globally by your distro’s package manager.
+Bundler is the tool that allows developers to keep a version
+controlled “Gemfile” that specifies all of the project’s dependencies
+and provides utilities to install and update those gems. These tools
+are crucial because Ruby developers often work with many applications
+that use different versions of Ruby and/or different versions of gems
+such as Rails. Traditional GNU/Linux distributions install packages
+to the global ,(code [/usr]) directory, limiting users to a single
+version of Ruby and associated gems, if they are packaged at all.
+Traditional package management fails to meet the needs of a lot of
+users, so many niche package managers have been developed to
+supplement them.])
+
+ (source-code
+ (scheme-source
+ ";;; guile-syntax-highlight --- General-purpose syntax highlighter
+;;; Copyright © 2015 David Thompson <davet@gnu.org>
+;;;
+;;; Guile-syntax-highlight is free software; you can redistribute it
+;;; and/or modify it under the terms of the GNU Lesser General Public
+;;; License as published by the Free Software Foundation; either
+;;; version 3 of the License, or (at your option) any later version.
+;;;
+;;; Guile-syntax-highlight 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 Lesser General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU Lesser General Public
+;;; License along with guile-syntax-highlight. If not, see
+;;; <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; Lexing utilities.
+;;
+;;; Code:
+
+(define-module (syntax-highlight lexers)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 regex)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-11)
+ #:use-module (srfi srfi-26)
+ #:export (make-cursor
+ cursor?
+ cursor-text
+ cursor-position
+ cursor-end?
+ move-cursor
+ move-cursor-by
+
+ lex-fail
+ lex-bind
+ lex-return
+ lex-lift
+ lex-map
+ lex-filter
+ lex-any*
+ lex-any
+ lex-all*
+ lex-all
+ lex-consume
+ lex-regexp
+ lex-string
+ lex-char-set
+ lex-delimited
+ lex-tag))
+
+(define (string-prefix?* s1 s2 start-s2)
+ (string-prefix? s1 s2 0 (string-length s1) start-s2))
+
+
+;;;
+;;; Cursor
+;;;
+
+(define-record-type <cursor>
+ (make-cursor text position)
+ cursor?
+ (text cursor-text)
+ (position cursor-position))
+
+(define (cursor-end? cursor)
+ \"Return #t if the cursor is at the end of the text.\"
+ (>= (cursor-position cursor) (string-length (cursor-text cursor))))
+
+(define (move-cursor cursor position)
+ \"Move CURSOR to the character at POSITION.\"
+ (make-cursor (cursor-text cursor) position))
+
+(define (move-cursor-by cursor offset)
+ \"Move CURSOR by OFFSET characters relative to its current
+position.\"
+ (move-cursor cursor (+ (cursor-position cursor) offset)))
+
+
+;;;
+;;; Lexers
+;;;
+
+(define (lex-fail cursor)
+ \"Always fail to lex STR without consuming any of it.\"
+ (values #f cursor))
+
+(define (lex-bind proc lexer)
+ \"Return a lexer that applies the result of LEXER to PROC, a
+procedure that returns a lexer, and then applies that new lexer.\"
+ (lambda (cursor)
+ (let-values (((result remainder) (lexer cursor)))
+ (if result
+ ((proc result) remainder)
+ (lex-fail cursor)))))
+
+(define (lex-return x)
+ \"Return a lexer that always yields X as the lex result.\"
+ (lambda (cursor)
+ (values x cursor)))
+
+(define (lex-lift proc)
+ \"Return a procedure that wraps the result of PROC in a lexer.\"
+ (lambda args
+ (lex-return (apply proc args))))
+
+(define (lex-map proc lexer)
+ \"Return a lexer that applies PROC to result of LEXER.\"
+ (lex-bind (lex-lift proc) lexer))
+
+(define (lex-any* lexers)
+ \"Return a lexer that succeeds with the result of the first
+successful lexer in LEXERS or fails if all lexers fail.\"
+ (define (either a b)
+ (lambda (cursor)
+ (let-values (((result remainder) (a cursor)))
+ (if result
+ (values result remainder)
+ (b cursor)))))
+
+ (fold-right either lex-fail lexers))
+
+(define (lex-any . lexers)
+ \"Return a lexer that succeeds with the result of the first
+successful lexer in LEXERS or fails if all lexers fail.\"
+ (lex-any* lexers))
+
+(define (lex-all* lexers)
+ \"Return a lexer that succeeds with the results of all LEXERS in
+order, or fails if any lexer fails.\"
+ (define (both a b)
+ (lambda (cursor)
+ (let-values (((result-a remainder-a) (a cursor)))
+ (if result-a
+ (let-values (((result-b remainder-b) (b remainder-a)))
+ (if result-b
+ (values (cons result-a result-b) remainder-b)
+ (lex-fail cursor)))
+ (lex-fail cursor)))))
+
+ (fold-right both (lex-return '()) lexers))
+
+(define (lex-all . lexers)
+ \"Return a lexer that succeeds with the results of all LEXERS in
+order, or fails if any lexer fails.\"
+ (lex-all* lexers))
+
+(define (lex-consume lexer)
+ \"Return a lexer that always succeeds with a list of as many
+consecutive successful applications of LEXER as possible, consuming
+the entire input text. Sections of text that could not be lexed are
+returned as plain strings.\"
+ (define (substring* cursor start)
+ (substring (cursor-text cursor) start (cursor-position cursor)))
+
+ (lambda (cursor)
+ (let loop ((cursor cursor)
+ (memo '())
+ (fail-start #f))
+ (if (cursor-end? cursor)
+ (values (reverse memo) cursor)
+ (let-values (((result remainder) (lexer cursor)))
+ (cond
+ ;; Regular successful result.
+ ((and result (not fail-start))
+ (loop remainder (cons result memo) #f))
+ ;; Successful result after some amount of unmatched
+ ;; characters.
+ ((and result fail-start)
+ (loop remainder
+ (cons* result (substring* cursor fail-start) memo)
+ #f))
+ ;; Consecutive failure.
+ (fail-start
+ (loop (move-cursor-by cursor 1)
+ memo
+ fail-start))
+ ;; First failure.
+ (else
+ (loop (move-cursor-by cursor 1)
+ memo
+ (cursor-position cursor)))))))))
+
+(define (lex-regexp pattern)
+ \"Return a lexer that succeeds with the matched substring when the
+input matches the string PATTERN.\"
+ (let ((rx (make-regexp (string-append \"^\" pattern))))
+ (lambda (cursor)
+ (if (cursor-end? cursor)
+ (lex-fail cursor)
+ (let ((result (regexp-exec rx (cursor-text cursor)
+ (cursor-position cursor))))
+ (if result
+ (let ((str (match:substring result 0)))
+ (values str (move-cursor-by cursor (string-length str))))
+ (lex-fail cursor)))))))
+
+(define (lex-string str)
+ \"Return a lexer that succeeds with STR when the input starts with
+STR.\"
+ (lambda (cursor)
+ (if (string-prefix?* str (cursor-text cursor) (cursor-position cursor))
+ (values str (move-cursor-by cursor (string-length str)))
+ (lex-fail cursor))))
+
+(define (lex-char-set char-set)
+ \"Return a lexer that succeeds with the nonempty input prefix that
+matches CHAR-SET, or fails if the first input character does not
+belong to CHAR-SET.\"
+ (define (char-set-substring str start)
+ (let ((len (string-length str)))
+ (let loop ((index start))
+ (cond
+ ((>= index len)
+ (substring str start len))
+ ((char-set-contains? char-set (string-ref str index))
+ (loop (1+ index)))
+ (else
+ (substring str start index))))))
+
+ (lambda (cursor)
+ (match (char-set-substring (cursor-text cursor) (cursor-position cursor))
+ (\"\" (lex-fail cursor))
+ (str (values str (move-cursor-by cursor (string-length str)))))))
+
+(define* (lex-delimited open #:key (until open) (escape #\\) nested?)
+ \"Return a lexer that succeeds with the string delimited by the
+opening string OPEN and the closing string UNTIL. Characters within
+the delimited expression may be escaped with the character ESCAPE. If
+NESTED?, allow for delimited expressions to be arbitrarily nested
+within.\"
+ (define (delimit str start)
+ (let ((len (string-length str)))
+ (let loop ((index start))
+ (cond
+ ;; Out of bounds.
+ ((>= index len)
+ len)
+ ;; Escape character.
+ ((eqv? escape (string-ref str index))
+ (loop (+ index 2)))
+ ;; Closing delimiter.
+ ((string-prefix?* until str index)
+ (+ index (string-length until)))
+ ;; Nested delimited string.
+ ((and nested? (string-prefix?* open str index))
+ (loop (delimit str (+ index (string-length open)))))
+ (else
+ (loop (1+ index)))))))
+
+ (lambda (cursor)
+ (let ((str (cursor-text cursor))
+ (pos (cursor-position cursor)))
+ (if (string-prefix?* open str pos)
+ (let ((end (delimit str (+ pos (string-length open)))))
+ (values (substring str pos end) (move-cursor cursor end)))
+ (lex-fail cursor)))))
+
+(define (lex-tag tag lexer)
+ \"Wrap the results of LEXER in a two-element tuple whose head is
+TAG.\"
+ (lex-map (cut list tag <>) lexer))
+"))
+
+ (p [Taking a step back, it becomes apparent that dependency isolation
+is a general problem that isn’t confined to software written in Ruby:
+Node has npm and nvm, Python has pip and virtualenv, and so on. A big
+limitation of all these language-specific package managers is that
+they cannot control what is outside of their language domain. In
+order to use RVM to successfully compile a version of Ruby, you need
+to make sure you have the GCC toolchain, OpenSSL, readline, libffi,
+etc. installed using the system package manager (note: I’ve seen RVM
+try to build prerequisites like OpenSSL before, which I then disabled
+to avoid duplication and security issues and I recommend you do the
+same.) In order to use Bundler to install Nokogiri, you need to make
+sure libxml2 has been installed using the system package manager. If
+you work with more than a single language, the number of different
+package management tools needed to get work done is staggering. For
+web applications, it’s not uncommon to use RVM, Bundler, NPM, Bower,
+and the system package manager simultaneously to get all of the
+necessary programs and libraries. Large web applications are
+notoriously difficult to deploy, and companies hire a bunch of
+operations folk like me to try to wrangle it all.])
+
+ (p [Anyway, let’s forget about Node, Python, etc. and just focus on
+Ruby. Have you or someone you work with encountered hard to debug
+issues and Git merge conflicts due to a problem with
+,(code [Gemfile.lock])? Bundler’s fast and loose versioning in the
+,(code [Gemfile]) (e.g. ,(code [rails >= 4.0])) causes headaches when
+different users update different gems at different times and check the
+resulting auto-generated ,(code [Gemfile.lock]) into the repository.
+Have you ever been frustrated that it’s difficult to deduplicate gems
+that are shared between multiple bundled gem sets? Have you looked at
+the ,(anchor [RVM home page] "https://rvm.io") and been frustrated
+that they recommend you to ,(code [curl bash]) to install their
+software? Have you been annoyed by RVM’s strange system of overriding
+shell built-ins in order to work its magic? I’m not sure how you
+feel, dear reader, but my Ruby environments feel like one giant,
+brittle hack, and I’m often enough involved in fixing issues with them
+on my own workstation, that of my colleagues, and on production
+servers.])
+
+ (p [So, if you’re still with me, what do we do about this? How can we
+work to improve upon the status quo? Just use Docker? Docker is
+helpful, and certainly much better than no isolation at all, but it
+hides the flaws of package management inside an opaque disk image and
+restricts the environments in which your application is built to
+function. The general problem of dependency isolation is orthogonal
+to the runtime environment, be it container, virtual machine, or “bare
+metal.” Enter functional package management. What does it mean for a
+package manager to be functional? GNU Guix, the functional package
+manager that I contribute to and recommend, has this to say:])
+
+ (blockquote
+ (p [GNU Guix is a functional package management tool for the GNU
+system. Package management consists of all activities that relate
+to building packages from sources, honoring their build-time and
+run-time dependencies, installing packages in user environments,
+upgrading installed packages to new versions or rolling back to a
+previous set, removing unused software packages, etc.])
+ (p [The term functional refers to a specific package management
+discipline. In Guix, the package build and installation process
+is seen as a function, in the mathematical sense. That function
+takes inputs, such as build scripts, a compiler, and libraries,
+and returns an installed package.]))
+
+ (p [Guix has a rich set of features, some of which you may find in
+other package managers, but not all of them (unless you use another
+functional package manager such as Nix.) Gem/Bundler can do
+unprivileged gem installation, but it cannot do transactional upgrades
+and rollbacks or install non-Ruby dependencies. Dpkg/yum/pacman can
+install all build-time and runtime dependencies, but it cannot do
+unprivileged package installation to isolated user environments. And
+none of them can precisely describe the ,(em [full]) dependency
+graph (all the way down to the C compiler’s compiler) but ,(em [Guix
+can]).])
+
+ (p [Guix is written in Guile, an implementation of the Scheme
+programming language. The upcoming release of Guix will feature a
+Ruby build system that captures the process of installing gems from
+,(code [.gem]) archives and a RubyGems import utility to make it
+easier to write Guix packages by using the metadata available on
+,(anchor "RubyGems.org" "https://rubygems.org"). Ruby developers
+interested in functional package management are encouraged to try
+packaging their gems (and dependencies) for Guix.])
+
+ (p [Now, how exactly can Guix replace RVM and Bundler? Guix uses an
+abstraction called a “profile” that represents a user-defined set of
+packages that should work together. Think of it as having many
+,(code [/usr]) file system trees that can be used in isolation from the
+others (without invoking virtualization technologies such as virtual
+machines or containers.) To install multiple versions of Ruby and
+various gems, the user need only create a separate profile for them:])
+
+ (source-code
+ "$ guix package --profile=project-1 --install ruby-2.2 ruby-rspec-3
+# Hypothetical packages:
+$ guix package --profile=project-2 --install ruby-1.9 ruby-rspec-2")
+
+ (p [A profile is a “symlink forest” that is the union of all the
+packages it includes, and files are deduplicated among all of them.
+To actually use the profile, the relevant environment variables must
+be configured. Guix is aware of such variables, and can tell you what
+to set by running the following:])
+
+ (source-code
+ "$ guix package --search-paths --profile=project-1")
+
+ (p [Additionally, you can also create ad-hoc development environments
+with the ,(code [guix environment]) tool. This tool will spawn a
+sub-shell (or another program of your choice) in an environment in
+which a set of specified packages are available. This is my preferred
+method as it automagically sets all of the environment variables for
+me and Guix is free to garbage collect the packages when I close the
+sub-shell:])
+
+ (source-code
+ "# Launch a Ruby REPL with ActiveSupport available.
+$ guix environment --ad-hoc ruby ruby-activesupport -E irb")
+
+ (p [In order to make this environment reproducible for others, I
+recommend keeping a ,(code [package.scm]) file in version control that
+describes the complete dependency graph for your project, as well as
+metadata such as the license, version, and description:])
+
+ (source-code
+ (scheme-source
+ "(use-modules (guix packages)
+ (guix licenses)
+ (guix build-system ruby)
+ (gnu packages)
+ (gnu packages version-control)
+ (gnu packages ssh)
+ (gnu packages ruby))
+
+(package
+ (name \"cool-ruby-project\")
+ (version \"1.0\")
+ (source #f) ; not needed just to create dev environment
+ (build-system ruby-build-system)
+ ;; These correspond roughly to \"development\" dependencies.
+ (native-inputs
+ `((\"git\" ,git)
+ (\"openssh\" ,openssh)
+ (\"ruby-rspec\" ,ruby-rspec)))
+ (propagated-inputs
+ `((\"ruby-pg\" ,ruby-pg)
+ (\"ruby-nokogiri\" ,ruby-nokogiri)
+ (\"ruby-i18n\" ,ruby-i18n)
+ (\"ruby-rails\" ,ruby-rails)))
+ (synopsis \"A cool Ruby project\")
+ (description \"This software does some cool stuff, trust me.\")
+ (home-page \"https://example.com\")
+ (license expat))"))
+
+ (p [With this package file, it is simple to an instantiate a
+development environment:])
+
+ (pre (code [$ guix environment -l package.scm]))
+
+ (p [I’m not covering it in this post, but properly filling out the
+blank ,(code [source]) field above would allow for building
+development snapshots, including running the test suite, in an
+isolated build container using the ,(code [guix build]) utility. This
+is very useful when composed with a continuous integration system.
+Guix itself uses ,(anchor "Hydra" "https://nixos.org/hydra/") as its
+CI system to perform all package builds.])
+
+ (p [As mentioned earlier, one of the big advantages of writing Guix
+package recipes is that the full dependency graph can be captured,
+including non-Ruby components. The pg gem provides a good example:])
+
+ (source-code
+ (scheme-source
+"(define-public ruby-pg
+ (package
+ (name \"ruby-pg\")
+ (version \"0.18.2\")
+ (source
+ (origin
+ (method url-fetch)
+ (uri (rubygems-uri \"pg\" version))
+ (sha256
+ (base32
+ \"1axxbf6ij1iqi3i1r3asvjc80b0py5bz0m2wy5kdi5xkrpr82kpf\"))))
+ (build-system ruby-build-system)
+ (arguments
+ '(#:test-target \"spec\"))
+ ;; Native inputs are used only at build and test time.
+ (native-inputs
+ `((\"ruby-rake-compiler\" ,ruby-rake-compiler)
+ (\"ruby-hoe\" ,ruby-hoe)
+ (\"ruby-rspec\" ,ruby-rspec)))
+ ;; Native extension links against PostgreSQL shared library.
+ (inputs
+ `((\"postgresql\" ,postgresql)))
+ (synopsis \"Ruby interface to PostgreSQL\")
+ (description \"Pg is the Ruby interface to the PostgreSQL RDBMS. It works
+ with PostgreSQL 8.4 and later.\")
+ (home-page \"https://bitbucket.org/ged/ruby-pg\")
+ (license license:ruby)))"))
+
+ (p [Note how the recipe specifies the PostgreSQL dependency. Below
+is the dependency graph for ruby-pg as produced by ,(code [guix
+graph]), excluding the GCC compiler toolchain and other low-level
+tools for brevity. Pretty neat, eh?])
+
+ (image/caption "/images/ruby-pg-graph.png"
+ "Abbreviated dependency graph for the pg gem")
+
+ (p [Given that Guix doesn’t yet have many gems packaged (help
+wanted), it can still be advantageous to use it for getting more
+up-to-date packages than many distros provide, but in conjuction with
+Bundler for fetching Ruby gems. This gets RVM out of your hair whilst
+creating a migration path away from Bundler at a later time once the
+required gems have been packaged:])
+
+ (source-code
+ "$ cd my-project/
+$ guix environment --ad-hoc ruby bundler libxml2 libxslt # etc.
+# A small bash script can be used to make these gem sets.
+$ mkdir .gems
+$ export GEM_HOME=$PWD/.gems
+$ export GEM_PATH=$GEM_HOME:$GEM_PATH
+$ export PATH=$GEM_HOME/bin:$PATH
+$ bundle install")
+
+ (p [As you’ve seen in the above package snippets, Guix package
+definitions are typically very short and rather easy to write
+yourself. The ,(code [guix import gem]) tool was made to lower ,(code
+ [foo bar]) the barrier even more by generating most of the boilerplate
+code. For example:])
+
+ (source-code "$ guix import gem pry")
+
+ (p [Produces this Scheme code:])
+
+(source-code
+ (scheme-source
+ [;;; hello there
+(+ 1 2 3)]))
+
+ (source-code
+ (scheme-source
+ "(package
+ (name \"ruby-pry\")
+ (version \"0.10.1\")
+ (source
+ (origin
+ (method url-fetch)
+ (uri (rubygems-uri \"pry\" version))
+ (sha256
+ (base32
+ \"1j0r5fm0wvdwzbh6d6apnp7c0n150hpm9zxpm5xvcgfqr36jaj8z\"))))
+ (build-system ruby-build-system)
+ (propagated-inputs
+ `((\"ruby-coderay\" ,ruby-coderay)
+ (\"ruby-method-source\" ,ruby-method-source)
+ (\"ruby-slop\" ,ruby-slop)))
+ (synopsis
+ \"An IRB alternative and runtime developer console\")
+ (description
+ \"An IRB alternative and runtime developer console\")
+ (home-page \"http://pryrepl.org\")
+ (license expat))"))
+
+ (p [One still has to package the propagated inputs if they aren’t
+yet available, add the necessary inputs for building native extensions
+if needed, and fiddle with the native inputs needed to run the test
+suite, but for most pure Ruby gems this gets you close to a working
+package quickly.])
+
+ (p [In conclusion, while support for Ruby in Guix is still in its
+early days, I hope that you have seen the benefits that using a
+general-purpose, functional package manager can bring to your Ruby
+environments (and all other environments, too.) For more information
+about Guix concepts, installation instructions, programming interface,
+and tools, please refer to the
+,(anchor [official manual] "https://gnu.org/software/guix/manual").
+
+Check out the ,(anchor [help] "https://gnu.org/software/guix/help")
+page for ways to contact the development team for help or to report
+bugs. If you are interested in getting your hands dirty, please
+,(anchor [contribute] "https://gnu.org/software/guix/contribute").
+Besides contributions of code, art, and docs, we also need ,(anchor
+[hardware donations] "https://gnu.org/software/guix/donate") to grow
+our build farm to meet the needs of all our users. Happy hacking!]))