diff options
Diffstat (limited to 'posts')
-rw-r--r-- | posts/2013-06-15-my-first-foss-contribution.skr | 39 | ||||
-rw-r--r-- | posts/2013-06-30-find-me-on-diaspora.skr | 57 | ||||
-rw-r--r-- | posts/2013-07-15-maine.skr | 34 | ||||
-rw-r--r-- | posts/2013-07-20-stumpwm-on-debian-wheezy.skr | 62 | ||||
-rw-r--r-- | posts/2013-08-07-guile-2d.skr | 48 | ||||
-rw-r--r-- | posts/2013-08-08-angularjs-post-mortem.skr | 135 | ||||
-rw-r--r-- | posts/2013-08-11-the-little-schemer.skr | 58 | ||||
-rw-r--r-- | posts/2013-08-17-font-rendering-pango-cairo.skr | 328 | ||||
-rw-r--r-- | posts/2013-09-22-liberating-a-thinkpad-x220.skr | 68 | ||||
-rw-r--r-- | posts/2013-09-27-guile-2d-0.1.skr | 31 | ||||
-rw-r--r-- | posts/2015-04-10-sxml-html-guile.skr | 235 | ||||
-rw-r--r-- | posts/2015-08-30-ruby-on-guix.skr | 578 |
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 <davet@gnu.org>")) + + (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!])) |