This is an old revision of the document!
OK, so let's start with some actual BBS programming! I've chosen Crystal for this tutorial, so you'll need to install it. At the moment this is being written, the current version is 0.21.1. As the language is still in alpha, a lot may break until it reaches 1.0 (scheduled for later in 2017) and I'll try to keep this updated.
I won't cover installation, since the official docs are pretty good at that. You will need a macOS or Linux setup, either native or in a virtual machine.
Once you have it installed, it's time to get going. Crystal has a “skeleton” too built-in that creates an initial app and creates a local Git repository as well. Git is not part of the tutorial, but you should always keep your code in check with a source code management system, so have a look at it when you can.
To create the initial skeleton, type
crystal init app bbs
This will create the skeleton inside the bbs
subfolder of the current directory. Jump into it and have a look around at the files created.
One thing the app creator leaves out is an easy way to compile the project - we can just type crystal build src/bbs.cr –release -o bbs_release
every time we want to compile, but that would get old really quick. So instead we'll create a Makefile
and use good old make to perform all these repetitive tasks on the project. Here's how it could look:
.PHONY: clean run: bbs ./bbs_release clean: rm -f bbs_release all: clean crystal build src/bbs.cr --release -o bbs_release bbs: rm -f bbs_release crystal build src/bbs.cr --release -o bbs_release
Make is also out of scope for this tutorial, but it won't get more complicated than this. From now on, just type make
in the project's root directory to erase the existing binary and compile a new one. If you type it now, you should see:
$ make rm -f bbs_release crystal build src/bbs.cr --release -o bbs_release ./bbs_release
That last line was our binary running, but of course it doesn't do anything yet!
Let's get the basics out of the way before we go into the meat of the problem. We want to store configuration settings, so let's do that first, and then move on to the main network loop.
Your src/bbs.cr
file should look like this now:
require "./bbs/*" module Bbs # TODO Put your code here end
Change it to:
require "./bbs/*" BBS::Main.new.go!
This will call a go!
method on the BBS module's Main
class. Let's create it, but on a separate file. Create a main.cr
file inside the src/bbs/
directory with:
require "yaml" module BBS class Main @config = YAML::Any def initialize load_config end def go! puts @config.inspect end private def load_config @config = YAML.parse(File.read("config.yml")) end end end
This file will be required by src/bbs.cr
(as mandated by its line 1) and will add the class to the BBS module. Let's create a basic config file as config.yml
on the root directory of the project (more info on YAML here:
settings: port: 2023
If you type make
now, you should see:
$ make rm -f bbs_release crystal build src/bbs.cr --release -o bbs_release ./bbs_release {"settings" => {"port" => "2023"}}
Yay, we're live with the basics and our settings are being read! Let's recap for a bit, since this class introduces a few important concepts.
require "yaml"
This line requires in Crystal's built in YAML handling functions.
@config = YAML::Any
This specifies the type of the @config
instance variable for the Main
class - this is a requirement in Crystal in order to generate runtime code for the variable. You'll notice that Crystal is very much like Ruby, but this is where the line is drawn - Crystal needs some type annotations in the code. You might think this is a step back from Ruby or even Python, but as a long time dynamic languages coder I can assure you that once a codebase exceeds a certain size, type safety being enforced by a compiler is a life saver. Also, it makes Crystal a bazillion times faster than Ruby or Python! :D
def initialize load_config end
As in Ruby, the constructor method for a class is called initialize
- in our case, the constructor just calls the load_config
method.
def go! puts @config.inspect end
This is the method called in bbs.cr
(our main entry point for the whole project)_after instantiating a new instance of the Main
class. At this stage, it just prints out the hash parsed from the config.yml
file that we created.
private def load_config @config = YAML.parse(File.read("config.yml")) end
Finally, the method that parses the configuration file. It's marked as private
since there's no real need for it being called outside of the Main
class. It just assigns the output of YAML.parse to the @config
instance variable. Note that there's no error handling whatsoever, as this is just a bare bones example.
So far, so good.