This contributing guide pertains to contributing to exercism problem sets in individual languages, as well as the exercism Problem API (x-api), which is a small application that delivers the problems in all of the languages.
The problem API includes each of the language repositories as a git submodule.
Submodules can be a bit tricky to work with, but in this case it's worth it, since contributing to a language track can be done with no dependencies on other languages.
You don't need to have a working Ruby development environment to contribute problems in Haskell or Erlang!
- Abstractions
- Code Arrangement
- config.json
- SETUP.md
- Existing Language Tracks
- New Language Tracks
- Problem Sets
- Metadata
- CLI
- Problem API
The core ideas in the codebase are:
- Progression - a complete list of all the problems currently available for
a language, in the order that they will be delivered by default. This is
based on the contents of the
EXERCISES.txt
file in the language-specific repository. - Problem - the basic unit of work in exercism. It is in a given language,
and has a
slug
that identifies it. An exercise consists of aReadme
andCode
. - Readme - a language-independent explanation of the problem to be solved.
- Code - supporting code to solve the problem. In its most basic form, it consists of a test suite, but there can be other supporting files (class or type definitions, metadata files, etc).
There is also the concept of Homework
which wraps the process of getting
a user's current exercise set from exercism.io, and getting all the relevant problems.
NOTE A problem is a README and test suite/supporting code (language+slug). An exercise is a problem worked by a user.
The sinatra app lives in lib/app
, and the business logic lives in
lib/xapi
. To determine whether something is business logic or web
application logic, think about whether or not a different interface (say,
perhaps a command-line interface) would also need that behavior.
The tests live under test
, and the path to the test file will mirror the
path to the code file, except with test
rather than lib
:
lib/app/routes/demo.rb
test/app/routes/demo_test.rb
There could be exceptions to this, but they are rare.
There are a number of keys in the config.json
of each language repository.
problems
- these are actively served viaexercism fetch
deprecated
- these were implemented, but aren't served anymoreforegone
- these must not be implemented in this languageignored
- these directories do not contain problems
The configlet
tool uses those categories to ensure that
- all the
problems
are implemented, deprecated
problems are not actively served as problems, andforegone
problems are not implemented.
In addition, it will complain about problems that are implemented but are not
listed in the config under the problems
key. This is where the ignored
key
is useful. Ignored directories don't get flagged as unimplemented problems.
A problem might be foregone for a number of reasons, typically because it's a bad exercise for the language.
Each language track may optionally contain a SETUP.md file in the root of the repository. This file should contain helpful, generic information about solving an exercism problem in the target language.
The contents of the SETUP.md file gets included in the README.md that gets
delivered along with the test suite and any supporting files when a user runs
the exercism fetch
command from their terminal.
At the very minimum, it should contain a link to the relevant language-specific documentation on help.exercism.io.
It would also be useful to explain in a generic way how to run the tests. Remember that this file will be included with all the problems, so it gets confusing if we refer to specific problems or files.
Some languages have very particular needs in terms of the solution. For
example, in Java, the solution is expected to go into the src/main/java/
directory.
If a language track has specific expectations, these should also be documented here.
To get a quick overview of which language tracks exist you can look at the problems directory in this repository (x-api).
To see which problems have been implemented in which language, check out the synopsis minisite.
Each language has its own repository, in the exercism GitHub organization. It
is named x<language>
, e.g.
You can also navigate to each language repository from this repository via the
problems
directory at the root of the repository. Each entry there is a link to
the relevant submodule's repository.
If there are any special considerations when contributing to a language, then these will be listed in the README for that specific repository.
Each problem consists of:
- a test suite
- an example solution
- supporting files (optional)
The test suite forms the basis for the problem. The example solution will not be delivered as part of the problem, and is simply used as a sanity check to make sure that the test suite makes sense.
In some language tracks the test suite is run against the example solution as part of the CI process.
Supporting files could be anything from additional documentation
(GETTING_STARTED.md
) or type definitions or helpful/boring/necessary
boilerplate.
If you're interested in adding problems for a language that we don't yet have, email me or tweet at me and I'll set up a new repo for that language.
Then you can fork and clone per usual.
Each language will need at least 10 problems in order to launch, as well as a small handful of people who can kick off the nitpicking. These people should have a good grasp of the styles and idioms in use in that language.
In addition to a problem set and some initial mentors, we also need:
- A help topic:
app/pages/languages/getting-started-with-$LANGUAGE.md
- (maybe) an addition to the exercism CLI that instructs the cli application how to recognize test files to prevent people from accidentally submitting tests as solutions.
Provided that I've remembered to add the language as a submodule to the x-api
repository, activating a language is as simple as setting active
to true
in the config.json
. Then the next time that I update the submodules (more
or less daily) it will be available via the exercism
CLI.
For a track that is set as "active": false
in the config.json
, exercism fetch
will not automatically pull down problems. You can still test the language by
fetching problems directly, e.g.:
exercism fetch cpp bob
This will allow you to do some dry-run tests of fetching exercises, double checking the instructions for each problem and submitting the problem solution for peer review.
It is recommended that you configure a Travis continuous integration build with your language track to verify that your example problem solutions satisfy the tests provided for each problem.
You can include advice and helpful links for your language track in the
SETUP.md
file.
The supporting code for a given problem can be found in language-specific
subdirectories under the problems
directory.
Each language-specific directory is a git submodule. If you wish to make
changes to a problem, look for the corresponding repository on GitHub under
exercism/x{LANGUAGE_NAME}
.
So, for example, the Scala examples can be found in this repository:
$ ls problems/scala
The corresponding git repository is at github.com/exercism/xscala.
The READMEs are constructed using shared metadata in the metadata
directory.
This is also a git submodule, and if you wish to make changes to it, the codebase
can be found on GitHub under exercism/x-common.
To contribute to an existing problem, fork, fix, and submit a pull request as usual.
It helps very much if the commit message contains the name of the problem, e.g.
Clarify failure output in queen-attack
If you have fixes for more than one problem, please use branches and submit multiple pull requests. This lets us merge simple and uncontroversial pull requests quickly, while leaving the option open to ask for improvements and have discussions in others.
If you have a global change, for example normalizing whitespace in all the problems, then it's fine to submit that in a single pull request.
A problem must have a unique slug. This slug is used as
- the directory name within each language-specific repository
- the basename for the metadata files
- the entry in
config.json
When creating a new problem there are a few things to keep in mind.
- The metadata needs to exist in the exercism/x-common repository.
- The problem needs an example solution.
- The problem slug must be added to the
config.json
.
If you are adding an exercise that already exists in another language, then the metadata files will already be present.
The example solution's file should contain the word example
or Example
in
it, to avoid delivering it as part of the problem.
The example is not shown to people, it doesn't have to be beautiful code, it's just there to make sure that the test suite works.
CI runs a sanity check on the config.json
using the configlet
tool.
Download the archive for your platform and architecture from the latest
release, unpack the
archive into your path.
To run the tool, call configlet .
from the root of the repository of the
language track, or from elsewhere you can call it with the path to the
repository (e.g. configlet /home/you/code/exercism/haskell
)
An exercise may have supporting files, such as type definitions.
Once you've created a problem, you'll probably want to provide feedback to people who submit solutions to it. Once the problem goes live, you can fetch it using the CLI:
$ exercism fetch <language> <problem>
Then submit the example solution that you wrote when creating the problem. This makes the problem available to you in the nitpick menu.
Remember to mark it with "OK, I'm done" if you don't want other people to comment on it.
The problem metadata is shared between all of the languages. This also lives in a separate repository (http://github.com/exercism/x-common), and is a collection of yaml, markdown, and json files. There are no development dependencies whatsoever in this repository, it's all just plain text.
There are three metadata files that go in the x-common repository.
<slug>.yml
- containsblurb
,source
, andsource_url
<slug>.md
- contains the long-form description of the exercise<slug>.json
- contains standard test data for the exercise
The .md and .yml are sewn into a README that gets delivered with the exercise. The .json can be used as a source of test cases for the test program. See exercism/x-common/README.md for more details of the .json file.
The <slug>.md
for each problem should contain a high-level, generic
description of the problem.
It should avoid specifics about data-structures, edge-cases, and error handling, since each language track can choose to handle these things quite differently.
The ~/.exercism.go
configuration file for the CLI contains a field
'hostname' which defaults to 'http://exercism.io'.
To reconfigure the CLI to talk to your local development environment, either
edit file directly, or use the configure
command:
exercism configure --host='http://localhost:4567'
If you are also serving exercises locally via the x-api
app, you can configure
your local exercism.io app to talk to the problems api by exporting an environment
variable:
$ export EXERCISES_API_URL=http://localhost:9292