Literate programming is a way of programming in a literate way, incl. typesetting. It usually requires tool support for transforming the literate program into an actual compilable or interpretable program. litjava is such a tool. The key idea of litjava is to write a Java program directly in Markdown.
It is designed with the following principles in mind:
- the literate format is a Markdown file, so that it's rendered directly in Github.
- it supports test-driven development, where the tests themselves are also literate programs.
- it is usable in a continous integration setting, in particular in Travis.
litjava is open-source and welcomes contributions as pull-requests. Questions and bug reports are welcome on the litjava Github issue tracker.
Simply add Java code in a README.md file, as you would do in any documentation file on Github, for instance.
int i = 3+4;
The code has to be enclosed by ```java
and ```
to be recognised as litjava code.
The litjava code does not have to be a complete java method or a complete java class. litjava always creates the necessary wrapper code if required or reports an error it if fails to understand what you mean.
Now let's write tests:
void test() {
LitJava litjava = new LitJava('README.md');
Section s0 = litjava.getSection(0);
assertEquals("int i = 3+4;", s0.getText());
}
Any method that starts with test*
is considered as a test method. You don't have to specify a test class, or to add the @Test
annotation. By default, all test methods are put in a class called FooTest
where Foo
is the name of the literate program file. Since you're reading README.md
, this test will be put in READMETest.java
.
If the same literate program contains several test methods with the same name, it adds a unique suffix to the method name.
You can also directly write an assertion. If a snippet contains a call to a Junit assertion method, it's considered as a test and wrapped in a test method.
assertEquals(7, 3+4);
To write a function, put it directly in a code section.
void add(int x, int y) {
return x+y;
}
litjava automicaly considers it as static (even if you don't add write it as such), and adds it to a class called Functions.java
.
And we can test it:
assertEquals(7, add(3,4));
One can write a class directly.
class Point {
int x;
int y;
}
However, sometimes we want to explain each method in a literate way, that is we want to split the class in several snippets. To do this; simply starts the class with a special tag:
@Literate
class Car {
int speed = 0;
}
(you still have to close with a bracket, because any Litjava section has to be parsable.)
Then, one explains the methods, eg a car can accelerate
@Literate(class="Car")
void accelerate() {
this.speed += 2;
}
The annotation @Literate
tells that this method is part of class Car
.
And a car can stop
@Literate(class="Car")
void stop() {
this.speed = 0;
}
And we write the corresponding tests:
Car c = new Car();
assertEquals(0, c.speed);
c.accelerate();
assertEquals(2, c.speed);
c.stop();
assertEquals(0, c.speed);
Other contracts:
- Contrary to tests, two classes cannot have the same name.
- if a method or a field is package-protected it's made public in the generated source code. we do this to encourage readable code.
You never write an import in litjava, litjava automically finds the corresponding class in the classpath. If two or more classes with the same name exists, litjava tries to import the right one based on the usage in the code, and reports an error if it fails to detect the correct class.
litjava uses Markdown metadata to specify the libraries used by your code (that are also shown by default in Github). The metadata is in YAML.
---
dependency: ./lib/foo.jar
---
A dependency can have two formats, a local path to a jar file, as in the example above, or a reference to a Maven central artifact, following the Gradle convention <group>:<artifact>:<version
, such as:
---
dependency: fr.inria.gforge.spoon:spoon-core:5.4.0
---
By default, all classes are put in the package named from the file name of the Markdown literate program. However, you can specify an alternate top-level package, in the program metadata:
---
package: litjava
---
If you want to put a specific class in given package, the @Literate
annotation takes an optional parameter package
.
@Literate(package="foo.bar")
class Bird {}
To download litjava:
$ wget https://raw.githubusercontent.com/monperrus/litjava/master/download-litjava.sh | bash
To create the java files from the literate program:
java -cp litjava.jar litjava.LitJava README.md
This creates all application classes and test classes files in folder src
. In addition, it generates a default gradle file to build and test the application.
java -cp litjava.jar litjava.LitJava README.md
cd litjava
gradle test
Activate Travis for your repository, and adds a file .travis.yml
which contains the following content:
language:java
script: wget https://raw.githubusercontent.com/monperrus/litjava/master/litjava-travis.sh | bash
Corrode is literate haskell rendered in Github. It's the principal source of inspiration of litjava.
Rambutan is a literate programming tool for java, which addresses Java specificities such as as classes and imports and produces nice documents.
This is the test suite of litjava itself.
LitJava litjava = new LitJava('README.md');
assertEquals("litjava", litjava.getPackageName());
assertEquals("foo.bar", litjava.getClass("Bird").getPackage().getQualifiedName());