Package tutorial

This tutorial shows how to create a Toit package.

We will recreate a simplified version of the toitware/toit-morse package which adds support for morse code. We have uploaded this version to https://github.com/toitware/toit-morse-tutorial.

Depending on how you installed your Toit SDK you may need to use slightly different commands. You can pick the correct tab for the examples below, depending on whether you installed the Jaguar tool or the standalone toitlang.org SDK.

Steps

Here are the common steps for creating a package:

  1. Develop code that should be shared with others.

  2. Polish the package, with eyes on:

    • The license

    • A README

    • Types

    • Toitdocs

    • Examples

    • Tests

  3. Create a package.yaml file.

  4. Tag and Upload.

  5. Publish to the Toit package registry.

Develop Code

In this section we write the Morse code library. We are simplifying the code a lot, so that it fits into the tutorial.

Usually, the code for a package is initially developed as part of an application. This can be a simple example program, or a bigger existing application. Once the code looks ready for other developers, the code that should become a package must be moved to its own repository. Indeed, a package is identified by its repository URL (together with its version number).

All package code lives in the src directory of the repository.

For the Morse example, we have only one file, src/morse_tutorial.toit.

We'll start with some constants:

DOT ::= 0
DASH ::= 1
SPACE-LETTER ::= 2

MORSE-CODE_ ::= {
  'a': [ DOT, DASH ],
  'b': [ DASH, DOT, DOT, DOT ],
  'c': [ DASH, DOT, DASH, DOT ],
}

/// The dash duration is three times that of the dot.
DASH-DURATION-FACTOR_ ::= 3
/// The duration between characters is three times the one of a dot.
SPACE-LETTER-FACTOR_ ::= 3

In the tutorial we only support the characters 'a', 'b', and 'c'. We are also skipping the constants necessary to encode word boundaries.

A simple function converts a string from ASCII to Morse code:

encode-string str:
  result := []
  str.do:
    result.add-all MORSE-CODE_[it]
    result.add SPACE-LETTER
  return result

Similarly, we want to have a function that emits the symbols with the correct timing:

emit symbols --dot-duration [--on] [--off]:
  symbols.do:
    if it == DOT or it == DASH:
      on.call
      sleep (it == DOT ? dot-duration : dot-duration * DASH-DURATION-FACTOR_)
      off.call
      sleep dot-duration // The space between symbols.
    else:
      assert: it == SPACE-LETTER
      // We already waited a dot-duration after the last character, so we have to
      // wait a bit shorter.
      sleep dot-duration * (SPACE-LETTER-FACTOR_ - 1)

There is much more functionality we could add, but for the purpose of this tutorial we are happy now. The next step is to polish the package.

Polishing

It's always a good idea to polish code, but for code that is published for external developers that it's even more the case.

License

Packages should have a license. We recommend a permissive free software license, such as MIT, or BSD for packages that are published publicly. This is not a requirement, but more restrictive licenses are often avoided and would lead to fewer users of a package.

For our tutorial we use the MIT license. Our LICENSE file thus has the following text in it:

MIT License

Copyright (c) 2021 Toitware

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

You should also add copyright headers to the source files.

// Copyright <year> <author>. All rights reserved.
// Use of this source code is governed by a <license>-style license that can be
// found in the LICENSE file.

README

Packages (and generally all repositories) should have a README.md file describing the package.

We recommend to have a title # <package-name> (or similar), followed by a paragraph that makes sense on its own and describes the project. The first paragraph should start as if preceded by "This is a package containing".

For the tutorial we add the following README.md (again, simplified to save space):

# Morse Tutorial

A tutorial version of the Morse package. Not intended for use as package.

Encodes string to Morse code. Also provides convenience methods
that call given blocks `on` and `off` with the correct timing.

## Usage

A simple usage example.

```
import morse-tutorial

main:
  ...
```

See the `examples` folder for more examples.

## Features and bugs

Please file feature requests and bugs at the [issue tracker][tracker].

[tracker]: https://github.com/toitware/toit-morse-tutorial/issues

Types

Toit doesn't require any types, but we strongly recommend to add some on API boundaries of a package. It helps new users to understand the package more easily, and also helps the IDE for exploratory development where users use auto-completion and similar IDE features to discover a package.

In our example we will change the signatures of the two exposed functions:

encode-string str/string -> List:

and

emit symbols/List --dot-duration/Duration [--on] [--off] -> none:

Toitdoc

Similar to types, it's always a good idea to have good documentation; even more so for API boundaries.

We suggest the following changes:

/**
The dot symbol.
Also known as "di", or "dit".
*/
DOT ::= 0

/**
The dash symbol.
Also known as "dah".
*/
DASH ::= 1

/**
A symbol representing the space between two characters.
*/
SPACE-LETTER ::= 2

/**
Encodes the given string $str to Morse code.
Returns a list of $DOT, $DASH and $SPACE-LETTER.
The characters of $str must only be equal to 'a', 'b' or 'c'.
*/
encode-string str/string -> List:
  ...

/**
Emits the given Morse symbols.
Uses the $on and $off blocks to turn the "emitter" on or off. The blocks
  could, for example, manipulate an LED, or a speaker.
The given $symbols should be Morse symbols. See $encode-string for a function
  that generates Morse symbols from a string.
*/
emit symbols/List --dot-duration/Duration [--on] [--off] -> none:
  ...

Examples

Examples are a good way to introduce users to the functionality of the package, and should thus contain typical code with lots of documentation.

A typical use case would be to emit morse on a GPIO pin (examples/gpio.toit):

// Copyright (C) 2021 Toitware ApS. All rights reserved.
// Use of this source code is governed by an MIT-style license that can be
// found in the LICENSE file.

import morse-tutorial
import gpio

/**
This example demonstrates the use of the morse package with an ESP32.
It assumes that pin 23 is connected to an LED (and an appropriate resistor).
*/

// Update the pin here if you use a different one.
LED-PIN ::= 23

main:
  pin := gpio.Pin.out LED-PIN

  10.repeat:
    morse-tutorial.emit
        morse-tutorial.encode-string "ababc"
        --dot-duration= Duration --ms=250
        --on=:  pin.set 1
        --off=: pin.set 0
    sleep (Duration --ms=3000)

Since this example uses the morse package, we need to install the package first. Instead of relying on the uploaded package (which would be a bit circular), we use the package locally:

# In the examples directory:
jag pkg init
jag pkg install --local --prefix=morse-tutorial ..

This creates a package.lock file mapping the prefix "morse-tutorial" to the package at location ...

We use jag pkg init to ensure that the package.lock file is created in the current directory. If we just used jag pkg install, we would risk adding a new dependency to our package and not to the example.

Tests

Toit doesn't have a testing framework, yet. For now, developers need to write their tests without much help from the Toit framework. We recommend to simply write executables that use the expect library to test the package. Tests should live in the tests folder.

Here is a simple test (tests/morse_test.toit) for ensuring that an 'a' is correctly translated:

// Copyright (C) 2021 Toitware ApS. All rights reserved.
// Use of this source code is governed by an MIT-style license that can be
// found in the LICENSE file.

import morse-tutorial show *
import expect show *

main:
  encoded := encode_string "a"
  expect-list-equals [DOT, DASH, SPACE_LETTER] encoded

Similarly to the examples, it's necessary to install the local package to be able to use it first:

# In the test directory:
jag pkg init
jag pkg install --local --name=morse-tutorial ..

The test should then execute successfully by running it locally:

jag run -d host morse_test.toit

Package.yaml

A package must have a package.yaml file at the root of the package.

Since we already added a license, we don't need to worry about the license. However, we need to add the "name" and "description" entries.

name: morse_package_tutorial
description: A tutorial version of the Morse package. Not intended for use as package.

Dependencies

We don't use dependencies in this package, but for completeness sake here is an example of how we would add one:

dependencies:
  prefix1:
    url: github.com/toitware/toit-morse
    version: ^1.0.0
  prefix2:
    url: github.com/toitware/toit-fahrenheit
    version: ^1.0.0

You normally don't need to worry about them, as they are automatically added when running jag pkg install <dependency> or toit.pkg install <dependency>.

Publishing to the Toit package registry

Toit packages are downloaded from the original sources, and (if published publicly) must be accessible through a public Git repository. The URL of the Git repository is part of the ID of a package (together with its version tag). As such, we advise to use the one of the following choices as repository name:

  • "toit-" followed by the package name.
  • the package name, or

The easiest way to publish a package is to upload the sources to GitHub and to use the publish action.

At each release it automatically takes the version number and informs the Toit package registry about the new release. Just make sure that the version is of the form v1.0.0 (or similar). Version numbers follow semantic versioning and tags must be valid semver versions with a prefixed 'v'.

Once published successfully, the registry will distribute a description of the package so that anyone can start using the package with a simple jag pkg install.

Publishing without a Github action

After committing all sources, add a tag yourself by executing git tag v1.0.0.

As the last step we need to inform a registry about the existence of the (new version of the) package. For the Toit package registry, publish your package here.