Stratus3D

A blog on software engineering by Trevor Brown

Introducing Asdf: The Extendable Version Manager

For Ruby you have rvm, rbenv, ry and a dozen other version managers.
For Elixir you have kiex and exenv.
For Erlang you have evm, erln8 and kerl.
For Node.js you have nvm and nvmw.

You get the idea. There are version managers for almost every language out there. Each of these languages have specific environmental requirements that must be met, each one slightly different from the others. But there are a lot of similarities between these tools. Most of them alter your PATH environment variable based on which version you have selected. They might also set a few other custom environment variables needed for the language to function properly. Some are more invasive and replace shell commands, like cd, with their own versions that handle version switching. But at the core, all of them alter your shell environment so that different executables are used based on the version you select. This is a common characteristic of all these version managers.

Using all these version managers also adds a lot of overhead as each is working to customize your environment. You have to ensure you have the version manager installed and configured correctly. You also have to ensure that the version managers don’t interfere with each other. Since they are all altering the shell environment there is a good chance they will affect each other in some way.

So what’s the solution to all this? Are we stuck juggling different version managers for everything? I did a lot of searching and asked a lot of questions online and found nearly a dozen open source projects out there aimed at solving this problem. But in this post I want to focus on one that I found to be particularly good at solving this issue - asdf.

Note that I was looking for a tool to help manage software versions in my development environment on my laptop. I don’t normally run different versions of the same VM or interpreter on production servers. Only in development do I have a need to for multiple language versions to be installed. As a result, all my thoughts in this post are about asdf in development, not production.

##What is asdf?

Extendable version manager with support for Ruby, Node.js, Elixir, Erlang & more

asdf is an extendable version manager with support for Ruby, Node.js, Elixir and Erlang (and now Lua). It was created by Akash Manohar (@HashNuke) and was designed to replace all the language-specific version managers. It’s also 100% shell script, which makes it easy to install and relatively portable. asdf doesn’t include support for any language directly. Each language is supported via a plugin that contains all the language specific version management details. asdf’s plugin system makes it easy to install, upgrade and remove plugins as needed. The plugin system also makes it easy to add support for new languages by third parties. asdf plugins are just plain old git repositories with a few shell scripts in them. Installation of a plugin can be done with the asdf plugin-add command.

Installation of different language versions is very easy as well. Versions can be set globally or on a per-directory basis with a .tool-versions file, which can define the language versions to be used for a directory. If you were working on a Ruby and Elixir project, you would normally set the Ruby version in one place (e.g. the .ruby-version file) and the Elixir version in another (e.g. the .exenv-version file). With asdf you create a .tool-versions file and define all the language version requirements for the project in it.

##Generic Version Managers Like This Already Exist, What Makes asdf Different?

asdf has several advantages over the other version managers available:

  • Simplicity - asdf is by far the most simple and easy to install
  • Complete set of version management features - asdf has simple commands to install, list, and remove software versions. Commands are also integrated with the .tool-versions file. There are also commands for adding, upgrading and removing plugins.
  • Simple method of setting defaults - All defaults can be set by creating a .tool-versions file in your home directory. If a subdirectory you are in doesn’t contain a .tool-versions file asdf crawls up the directory tree looking for one. If it gets to your home directory without finding one it uses the defaults you have set there.
  • Extendable - Plugins are easy to install and write and can be designed to do almost anything you want.

Below I list a few of the more popular projects along with some of the issues I see with them and some of the features asdf has that they lack. These are good projects with a lot of great people behind them. This comparison isn’t to bash the alternatives, but rather to compare their differences. Most of these projects offer more flexibility than asdf in some areas, and as a result often lack some of the features present in asdf.

While features of many of these tools are similar to asdf, none match the simplicity and easy of use of asdf. asdf is 100% shell script, whereas the alternatives are partially implemented in other languages like Python, Go and C. The addition of another language often makes installation more difficult and usually adds several more dependencies to the tool. Many of these tools also require you to write shell scripts to setup the environment for each project which is something I find very tiresome when setting up small projects.

##But asdf Doesn’t Support the Language I Use!

There is a plugin API that is extremely simple and very well documented (I can attest to this since I developed the Lua plugin for asdf). Development of a plugin for a new language or framework is straightforward and can be done in a couple of hours.

##How Does It Work?

asdf uses shims to ensure the right executable is run. Shims are stored in a directory that is part of your PATH. When you execute a shim that shim figures out your current directory and looks for a .tool-versions file in it. If one exists it reads the version information and determines which one of the real executables should be executed. For example, suppose you have ruby 2.2.4 and ruby 1.9.3 installed. Each install includes an irb executable but neither are on your PATH. If you are in a project that uses Ruby 2.2.4 (because it’s defined in your .tool-versions file), the irb executable in the 2.2.4 install is the executable that should be run. The shim will see that your in a project that requires Ruby 2.2.4 and look for the irb executable in that Ruby install and execute it. If for some reason you didn’t have Ruby 2.2.4 installed the executable wouldn’t be found (since that Ruby installation doesn’t exist on your machine) and ruby 2.2.4 not installed will be printed by the shim. Since this only happens when you run an executable the asdf overhead is kept to a minimum. Of course there are many more details in the source code that I won’t bother to mention here.

##How Do I Use It?

####Installation Installation is simple:

$ git clone https://github.com/HashNuke/asdf.git ~/.asdf

# For Ubuntu or other linux distros
$ echo '. $HOME/.asdf/asdf.sh' >> ~/.bashrc

# OR for Max OSX
$ echo '. $HOME/.asdf/asdf.sh' >> ~/.bash_profile

Then install the dependencies:

  • On OSX install automake autoconf openssl libyaml readline libxslt libtool unixodbc with homebrew.
  • On Ubuntu install automake autoconf libreadline-dev libncurses-dev libssl-dev libyaml-dev libxslt-dev libffi-dev libtool unixodbc-dev with apt-get.

That’s it! You should be able to view asdf help by running asdf in the shell.

####Usage

Say we want to install ruby 2.2.4 for a project we are going to build. First we have to install the ruby plugin (you will only need to do this once):

$ asdf plugin-add ruby https://github.com/HashNuke/asdf-ruby.git

Now we can install ruby 2.2.4:

$ asdf install ruby 2.2.4

Next we just cd into our project directory and create a .tool-versions file inside it containing the ruby version:

$ echo 'ruby 2.2.4' >> .tool-versions

That’s it! Now we can use all the executables that came with the Ruby 2.2.4 install in our project.

##Summary

I have been using asdf exclusively at work and at home for almost 4 months now. I have been able to uninstall a lot of software and simplify my dotfiles a lot since switching to asdf. Overall it has provided a big a boost to my productivity. Now I don’t even think about version management most of the time. With the few Erlang version managers available I was always struggling to come up with a good strategy for Erlang version management. With asdf, Erlang version management just works. I hope you will take a look at the project and give it a try. You won’t regret trying it.

##Resources

  • asdf: https://github.com/HashNuke/asdf
  • asdf-lua: https://github.com/Stratus3D/asdf-lua
  • http://cazrin.net/blog/2016/my-favourite-version-management-tool/

Migrating From BitBucket to Local GitLab Server

GitLab CE

I decided to move away from BitBucket Git repository hosting to a self-hosted Git solution. Since I am familiar with Ruby on Rails I decided use GitLab to host my repositories since GitLab version 8 is a Rails 4.1 application. GitLab originally used Gitolite to handle the Git repository hosting but they have moved away from Gitolite to plain old Ruby code. I already had a server running Ubuntu 14.04 and I figured it would be perfect for running GitLab.

##Installation It turns out the GitLab installation instructions for Ubuntu are straightforward.

First we need to install OpenSSH server, Postfix, and a few other things:

$ sudo apt-get install curl openssh-server ca-certificates postfix

Since I didn’t need email I chose the default options when I went through the Postfix configuration process. Now we can install GitLab:

$ curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
$ sudo apt-get install gitlab-ce

The curl command configures apt-get with the information it needs to fetch and install the GitLab package. If you want to see the contents of the script before you run it you can pipe it to cat or less instead of sudo bash. apt-get then installs GitLab.

##Configuration After installation run:

$ sudo gitlab-ctl reconfigure

After installing check the status of GitLab:

$ sudo gitlab-ctl status

Everything should be running. If GitLab is not running try starting it manually by running:

$ sudo gitlab-ctl start

Now that GitLab is running you should be able to navigate to your server’s IP address or hostname in your browser and see the login page.

GitLab CE

Sign in as root with the default password (5iveL!ve) and then change your password. After changing the password you should be able to navigate web interface to customize your profile, setup permissions and create repositories.

##Repository Migration

After getting GitLab installed and the permissions setup the next step is to migrate the BitBucket repositories to the GitLab server. Unfortunately importing BitBucket repositories is a little confusing and it took me quite a while to figure out how to import them. The difficulties are mostly due to the way the BitBucket API works.

###Create an OAuth Key

We first need to create an OAuth consumer key so that GitLab can access private repositories. To create an OAuth key sign into your BitBucket account and navigate to settings > Oauth. Then click “Add consumer” and create a consumer for GitLab. The callback URL should be the URL to your GitLab instance. The URL could be anything, but it makes sense to also make it the URL to your GitLab instance. Under the Permissions heading make sure the OAuth consumer has read and admin permissions to repositories. It may seem a little odd to giving GitLab admin permissions to repositories, but this is needed in order to GitLab to be able to clone the repositories. The BitBucket import script will use the BitBucket API to add the public key as a deploy key for each repository your import, then clone down the repository via SSH and remove the deploy key once the import is complete. This is necessary because the BitBucket API doesn’t allow repositories to be cloned by OAuth applications via HTTPS. In order to add a deploy key to a repository GitLab must have admin access to the repositories being imported, since adding keys is an admin-level action.

After creating the OAuth consumer make a note of the key and secret values. You will need them when you configure GitLab to use the OAuth consumer.

###Configure GitLab with the OAuth Consumer

Update gitlab.rb configuration:

$ sudo vi /etc/gitlab/gitlab.rb

Add an omniauth_providers section to the file:

gitlab_rails['omniauth_providers'] = [
   {
     "name" => "bitbucket",
     "app_id" => "YOUR_KEY",
     "app_secret" => "YOUR_APP_SECRET",
     "url" => "https://bitbucket.org/"
   }
]

Use the key and secret values you got from the BitBucket interface in place of YOUR_KEY and YOUR_APP_SECRET in the BitBucket section of the file.

###Create an SSH Public Key for the GitLab Import Script

Next, you need to create a SSH public key on the server you have GitLab installed on. This key must NOT be associated with ANY existing BitBucket accounts. If it is the import will fail with an “Access denied! Please verify you can add deploy keys to this repository.” error. The BitBucket import script will use the BitBucket API to add this public key as a deploy key for each repository you choose to import. If the key is already being used by a BitBucket account, the API call to add the key will fail since the key is already being used. To be safe, generate a brand new key. The key should be generated for the git user:

$ sudo -u git -H ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/var/opt/gitlab/.ssh/id_rsa): bitbucket_rsa

When prompted for the name of the file to store the key in, enter bitbucket_rsa. Mine ended up being stored at /var/opt/gitlab/.ssh/bitbucket_rsa. The documentation says that the location should be /home/git/.ssh/bitbucket_rsa. Apparently my git user’s home directory was /var/opt/gitlab/ instead of /home/git/. It seemed to work just fine in /var/opt/gitlab/. I believe the only thing that is important is that the key is placed in the git user’s home directory. You can verify the home directory of the git user by running echo ~git. This is a known issue with the documentation and apparently /var/opt/gitlab/ is correct.

Your SSH client will also need to be configured to use this key when connecting to BitBucket. You can do that by adding this to the SSH config (mine was located at /var/opt/gitlab/.ssh/config, you may need to create the file):

Host bitbucket.org
  IdentityFile ~/.ssh/bitbucket_rsa
  User git

Next, to verify you have the ssh key setup correctly, run:

$ sudo -u git -H ssh -Tv bitbucket.org

You should see output along the lines of what is shown below and be prompted continue connecting:

OpenSSH_6.6.1, OpenSSL 1.0.1f 6 Jan 2014
debug1: Reading configuration data /var/opt/gitlab/.ssh/config
...
debug1: Server host key: RSA 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40
The authenticity of host 'bitbucket.org (131.103.20.167)' can't be established.
RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40.
Are you sure you want to continue connecting (yes/no)?

Type yes to confirm that you want to connect to bitbucket.org. After confirming you should see a permission denied message. If you see a authentication successful message you have done something wrong. The key you are using has already been added to a BitBucket account and will cause the import script to fail. Ensure the key you are using CANNOT authenticate with BitBucket.

###Update GitLab with the New Configuration

Next all you need to do is tell GitLab to reconfigure itself with the changes made in gitlab.rb. To reload the configuration run:

$ sudo gitlab-ctl reconfigure

###Import Repositories

You should now be able to go to the web interface and import a repository. Go to your GitLab server’s homepage and click the “New Project” button. On the page the third row in the form should say “Import project from” and have a list of third party sites. Click on the BitBucket button. You should be taken to the BitBucket OAuth authorization page (You will need to be logged into your BitBucket account for this to work). Allow the GitLab consumer access to your account. After you allow access to your account you should be taken back to your GitLab website. The page should have a list of all your BitBucket repositories with an import button for each one.

BitBucket Import

Try clicking the import button for an individual repository, or click “Import All Projects”. If everything is working a spinner icon should appear and after a few seconds the import should complete. Once the import is complete the repository will be just like any other repository in GitLab. You can edit the repository and customize it in any way you see fit.

If something goes wrong during import and you get the “Access denied! Please verify you can add deploy keys to this repository.” you can alter the add_deploy_key method in the importer so that more readable error messages are logged to the production log.

$ sudo vi /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/bitbucket_import/client.rb

Then update the add_deploy_key method so it looks like this:

def add_deploy_key(project_identifier, key)
  deploy_key = find_deploy_key(project_identifier, key)
  Rails.logger.info("=== add_deploy_key #{project_identifier}, #{key}, #{deploy_key}")
  return if deploy_key

  response = api.post("/api/1.0/repositories/#{project_identifier}/deploy-keys", key: key, label: "GitLab import key")
  Rails.logger.info("=== add_deploy_key: #{response.inspect}")
  Rails.logger.info("=== add_deploy_key response body: #{response.body}")
  JSON.parse(response.body)
end

The additional logging calls will make it easy to diagnose the problem. Restart GitLab and try to import the repositories again. Then check the production log (sudo tail -f /var/log/gitlab/gitlab-rails/production.log).

##Migrating GitHub Repositories

Importing GitHub repositories is easier than importing repositories stored on BitBucket.

First you need to do is register a new application on GitHub. Go to Settings > Applications > Developer Applications > Register new application. The homepage URL should be the URL of your GitLab instance. Fill in the form and submit it. Make a note of the Client ID and the Client Secret after you create the application.

###Add An OmniAuth Provider for GitHub

Next you need to add another OmniAuth provider for GitHub. All you need to do is add another hash to the gitlab_rails['omniauth_providers'] array. Edit gitlab.rb again:

$ sudo vi /etc/gitlab/gitlab.rb

The file should like this:

gitlab_rails['omniauth_providers'] = [
    {
    # ... BitBucket OmniAuth settings
    },
    {
      "name" => "github",
      "app_id" => "YOUR_APP_ID",
      "app_secret" => "YOUR_APP_SECRET",
      "url" => "https://github.com/",
      "args" => { "scope" => "user:email" }
    }
]

YOUR_APP_ID and YOUR_APP_SECRET should be set to the Client ID and Client Secret values shown in the GitHub interface. Next reconfigure GitLab:

$ sudo gitlab-ctl reconfigure

###Import Repositories

Now go to GitLab and click “New Project”. Under the “Import project from” heading click “GitHub”. You will be taken to GitHub and prompted to give the application you created permission to you account. You will need to be logged in to your GitHub account for this to work. After granting permission to the application you will be taken back to GitLab and shown a list of all GitHub repositories. Choose “Import All Projects” or import individual repositories.

##Backing Up GitLab Itself

When I was setting up my GitLab server I came across a script that backs up GitLab data to a remote server - Auto GitLab Backup. Since I don’t have another server or service to backup the data on my GitLab server I wasn’t able to use this script. If you want another copy of your Git repositories all you would need to do would be to setup another server (maybe an EC2 instance?) and then add the Auto GitLab Backup script to your crontab on your GitLab server.

##Conclusion

GitLab is a good alternative to BitBucket or GitHub. It’s great to have a completely open source piece of software that I can run on my own servers. The BitBucket import tool is definitely be tricky to setup but it’s great that GitLab provides the tools for migrating repositories. Since it does lack some of the features present in BitBucket and GitHub it may be not be suitable for large teams. But it’s perfect for individuals or small teams that want to be independent of BitBucket and GitHub.

##Resources

Introduction to Lua

This was a talk I gave at the Sarasota Software Engineers User Group in Sarasota on August 26, 2015

  • Abstract: Most popular scripting languages now contain many paradigm specific and platform specific features that increase the complexity of the language. Few of these languages are simple enough to be embedded in existing applications easily. Lua’s extensibility and simplicity makes it an ideal choice for embedded scripting. Lua is often embedded in video games, desktop GUI applications, server software, and even mobile applications. This talk covers the basics of the Lua. I will demonstrate how Lua’s simplicity allows for quick development of small utility scripts. I will also show how Lua’s powerful meta-mechanisms allow for rapid development of software in prototypal OO, classical OO, and functional paradigms. At the end of the talk I will integrate Lua into an existing application.
  • Slides: https://speakerdeck.com/stratus3d/introduction-to-lua

I started learning Lua at the end of 2013 for a project that I was going to be working on. At the beginning of 2014 I started on the project - using Corona SDK to develop a simple native mobile app for Android and iOS. Development took about two months to complete. Corona SDK has a Lua API that allows you to write native iOS and Android apps in Lua code. Overall Corona SDK works pretty well. The documentation seems to be out of date in some places but in general was pretty good. The API did have a few quirks but it is more than sufficient for mobile games. Business apps are more of a challenge as the API is oriented more towards game type functionality, such as sprites, 2D physics and sound. In this post I am not going to talk about Corona but rather Lua, programming language itself.

I have really enjoyed programming in Lua. I only used it for about two months and it took me less than 2 weeks to learn.

Lua is a lightweight, interpreted programming language implemented in ANSI C. It is multi-paradigm, allowing you to write procedural, object-oriented, or prototypal code. It was created in 1993 by Roberto Ierusalimschy. It’s open source and has been released under the MIT license. While Lua is often embedded in larger applications to allow for extension via Lua scripting, it’s not limited to just that. It’s efficiency and extensibility make it possible to write large standalone applications in Lua. Lua is also one of the easiest languages to learn.

In this post I want to highlight the strengths of Lua as well as list the some well known applications that use it.

##Where is Lua Used? Here is a list of a few prominent pieces of software that use Lua for scripting/plugins/extensions:

  • Adobe Photoshop Lightroom - for the UI and plugins
  • Freeswitch - embedded scripting
  • Lego Mindstorms NXT - scripting
  • Redis - embedded scripting
  • NeoVim - plugins
  • Wireshark - plugins
  • Both Nginx and Apache have Lua modules.

This isn’t a complete list! Lua seems to appear almost everywhere. Many other enterprise applications have a embedded Lua interpreters for scripting. Lua is also commonly used in game development due to it’s performance and short learning curve. Dozens of games use Lua for plugins and extensions, as well as core game logic.

##Strengths of Lua

###Simplicity

The first thing you will notice when learning Lua is how simple it is. The syntax is very clean and feels similar to Ruby or Basic. The control structures are simple and there are only a few of them. There are only a handful of data types. The complete list is: nil, boolean, number, string, table, function, userdata and thread. That’s it. There aren’t different types of numbers, they are all just numbers (they are stored as IEEE 754 floats). There aren’t arrays, tuples, and associative arrays, there are only tables. Tables are simple collections of key-value pairs that can be treated as arrays, objects or any other sort of compound data structure. Here is a short example of the different types and how they work (note that comments are started with “–”):

-- numbers
local one = 1
local one_and_a_half = 1.5

-- strings
local greeting = "Hello World"

-- tables
local empty_table = {}
local translations = {
    'one' = 1,
    'two' = 2,
    'three' = 3,
    'four' = 4,
    'five' = 5
}
-- table functioning like an array
local array_like = { "a", "b", "c" }
-- table indexes start at 1
print(array_like[1]) --> "a"

-- functions
local function word_to_number(word)
    local number = translations[word]
    if not number then
      return "word must be greater than 5"
    else
      return number
    end
end
-- invoke the function
word_to_number('three') --> 3

Those are the most commonly used first class values in Lua. There isn’t really anything else. Everything else can be created with some combination of tables, functions and the other data types. Control structures are very simple as well. There are if statements and while, repeat, and for loops. There are also break and return commands. Lua does not have a case statements but haven’t ever found myself needing them. Pretty much everything you would do with a case statement can be done with if/elseif statements or lookup tables.

-- if/elseif/else
if a => 1 then
  return "too big"
elseif a =< 0 then
  return "too small"
else
  return "somewhere between 0 and 1"
end

-- for loops
-- numeric for loop
for i = 1, math.huge() do
  -- this will loop forever unless a = true
  if a then
    break
  end
end

-- generic for loop
for k, v in pairs(translations) do
  -- loops over all the numbers in translations table and print key-value pairs one at a time
  print(k, v)
end

Lua’s simplicity and it’s similarity to JavaScript make it a great first programming language. With the libraries and game frameworks out there anyone new to the language can quickly put there skills to use building simple applications. Lua’s simplicity also makes it a very easy language teach.

###Extensibility

Lua’s simplicity also makes Lua relatively flexible. You can write procedural or object-oriented applications. You can create classical or prototypal objects. Since there are no classes or objects, you might think that implementing any sort of classical object oriented functionality in Lua would be difficult. This is not the case, using Lua’s tables and metatables it is relatively simple to create classes. Lua does tend to lend itself to a more prototypal object model but that doesn’t mean we can’t create our own classes. In order to understand how classes are created we first need to know how metatables work:

-- metatables are similiar to JavaScript prototypes and provide
-- a sort of operator overloading.
-- metatables can contain various attributes that are read by Lua when
-- performing certainly operations. For example,
-- __index is a metatable attribute Lua checks when a key is missing in a table.
defaults = {favorite_language = 'Lua'}
defaults.__index = defaults
user = {}

-- If we set `defaults` as the metatable Lua will check the table assigned
-- to the metatables `__index` attribute if a key is missing.
setmetatable(user, defaults)
print(getmetatable(user)) --> table: 0x15ebe60
print(user.favorite_language) --> 'Lua'

-- there are many other metatable attributes - __newindex, __mode, __call, __metatable,
-- __tostring, __len, __gc, __eq, __lt, __le, __unm, __add, __sub, __mul, __div,
-- __mod, __pow, __concat

Metatables are very powerful and are used for many things. Now that we have seen how they work let’s use them to create a class:

-- With metatables creating objects and classes is easy.
local User = {}
User.__index = User

function User:new(first_name, last_name, favorite_language)
    local user = {
        first_name = first_name,
        last_name = last_name,
        favorite_language = favorite_language
    }
    setmetatable(user, User)
    return user
end

-- This:
function User:display_name()
    return self.first_name .. ' ' .. self.last_name
end

-- Is shorthand for this:
function User.display_name(self)
    return self.first_name .. ' ' .. self.last_name
end

-- The display_name function will be used to print the user
User.__tostring = User.display_name

local u = User:new('Joe', 'Armstrong', 'Erlang')
print(u:display_name()) --> 'Joe Armstrong'
print(u) --> 'Joe Armstrong'

That is pretty much all there is to creating classes. Of course this is a very simple example. Other object-oriented features like inheritance and information hiding can also be implemented with a combination of metatables and closures.

Lua can also be extended easily with C/C++. It has really easy to use C interface and there are many open source libraries for interfacing between Lua and other programming languages.

###Efficiency

Lua is one of the fastest interpreted languages out there today. It is often significantly faster Ruby or Python. This is no doubt due in part to Lua’s simplicity. LuaJIT makes it even faster.

###Portability

Lua runs on any architecture that can execute ANSI C code. That means Lua runs nearly everywhere. Almost any platform you can think of can run Lua. In addition to all the major desktop operating systems, Lua runs on Android, iOS, Kindle, Nook, Xbox, Playstation, Raspberry Pi, Adruino, and more. Since Lua is so portable it is a great choice for any piece of software that needs to run on multiple platforms. This is one of the reasons Lua is often used for cross-platform games.

###Conclusion

Lua is a great language with some very unique strengths. It’s hard to beat Lua’s combination of flexibility, simplicity and performance.

Hopefully this has post has gotten you interested in Lua. If you haven’t ever used it before I encourage you to give it a try. If you are an experienced programmer you will likely find a few things annoying (one-based indexes for tables for example). And while you won’t likely end up writing large pieces of software in Lua there is a chance something in your large applications could benefit from a little Lua code (HTTP request preprocessing in nginx or Apache perhaps?). If your new to programming Lua’s simplicity will make it easy to learn the basics and become familiar with the programming constructs that are present in most other languages.

###Resources