snippet-compiler is a simple command line tool for compiling snippets of code (i.e. C and C++).
It is useful for quickly compiling some code and optionally running the resulting executable. I
wrote it so that I could test code snippets in Markdown slides and README files (see the snippet-compiler-markdown-render
command below).
$ pip install snippet-compiler
The snippet-compiler
command reads source code from standard input, saves it to a temporary file, and invokes a compiler on it.
$ echo -n '#include<iostream>\nint main(){return 0;}' | snippet-compiler
This isn't very interesting since nothing will be printed, but if there is a compiler error, it will be printed.
$ echo -n '#include<iostream>\nint ain(){return 0;}' | snippet-compiler
main.cpp: In function āint ain()ā:
main.cpp:1:11: warning: no return statement in function returning non-void [-Wreturn-type]
1 | int ain(){}
| ^
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x24): undefined reference to `main'
collect2: error: ld returned 1 exit status
To run the executable after compiling, pass the --run
option.
$ echo -n '#include<iostream>\nint main(){std::cout << "hello world" << std::endl; return 0;}' | snippet-compiler --run
hello world
To change the compiler command, pass a Python template string to --compiler-command
echo -n "#include <concepts>\nint main(){}" | snippet-compiler
main.cpp:1:10: fatal error: concepts: No such file or directory
1 | #include <concepts>
| ^~~~~~~~~~
compilation terminated.
$
$ echo -n "#include <concepts>\nint main(){}" | snippet-compiler --compiler-command 'g++-10 -std=c++20 {file}'
The {file}
tag is required to tell the compiler where the name of the temporary file should go in the command line.
By default, only the snippet text will be included in the source file. You can also specify a template file to use
// my-snippet-template.cpp
#include<iostream>
int main()
{
{snippet}
return 0;
}
The string {snippet}
in the template will be replaced with the code snippet before compiling.
$ echo -n 'std::cout << "hello world" << std::endl;' | snippet-compiler --run
hello world
The snippet-compiler
package now includes some other commands for working with snippets.
snippet-compiler-markdown-render
started off as a simple command that would read a markdown file,
look for code snippets (identified by a control block), compile the snippets
using snippet-compiler
, and insert the output into the document. It has since had more features
added that are useful for writing up example code in READMEs or presentations.
Here's an example:
To write a hello world program in C++, we will need to include the `iostream` header
<!---
tag: example-1
-->
```cpp
#include<iostream>
int main()
{
cout << "Hello World!\n";
}
```
However, when we try to compile this, we will get an error
<!---
tag: example-1
-->
```bash
this code fence will be replaced
with the compiler output of the above example.
```
This is because the `cout` object is in the `std::` namespace, which is easy to forget.
<!---
tag: example-2
snippet-compiler:
flags:
- run
-->
```cpp
#include<iostream>
int main()
{
std::cout << "Hello World!\n";
}
```
However, when we try to compile this, we will get an error
<!---
tag: example-2
-->
```bash
this code fence will be replaced
with the output of the above example.
```
If we save this in a file named slides.md
, then we can run the snippet-compiler-markdown-render
command
$ snippet-compiler-markdown-render slides.md
To write a hello world program in C++, we will need to include the `iostream` header
<!---
tag: example-1
-->
```cpp
#include<iostream>
int main()
{
cout << "Hello World!\n";
}
```
However, when we try to compile this, we will get an error
<!---
tag: example-1
-->
```bash
main.cpp: In function āint main()ā:
main.cpp:4:3: error: ācoutā was not declared in this scope; did you mean āstd::coutā?
4 | cout << "Hello World!\n";
| ^~~~
| std::cout
In file included from main.cpp:1:
/usr/include/c++/9/iostream:61:18: note: āstd::coutā declared here
61 | extern ostream cout; /// Linked to standard output
| ^~~~
```
This is because the `cout` object is in the `std::` namespace, which is easy to forget.
<!---
tag: example-2
snippet-compiler:
flags:
- run
-->
```cpp
#include<iostream>
int main()
{
std::cout << "Hello World!\n";
}
```
Now the program and compiles, and if we run it we will get
the following output:
<!---
tag: example-2
-->
```bash
Hello World!
```
This is useful when your writing slides that include code examples.
If you use vim (and why wouldn't you), snippet-compiler-markdown-render
can actually be used as a "formatter" with vim-autoformat
. Just put these lines in your vim config:
let g:formatdef_snippet_compiler_markdown_render = '"snippet-compiler-markdown-render"'
let g:formatters_markdown = ['snippet_compiler_markdown_render']
and running :Autoformat will replace all of the output blocks for code examples.
The control block that appears above the snippet will be parsed as YAML and supports some basic settings
Required
The tag setting is used to match output blocks with input blocks.
The standard library functions and classes live in the `std::` namespace. If you forget to prefix a standard utility like
`cout` with `std::`
<!---
tag : example-1
-->
```cpp
#include<iostream>
int main() {
cout << "Hello World";
}
```
You will get an error like this
<!---
tag : example-1
-->
```bash
This will be replaced with the compiler output, which will fail because cout is called without 'std::'
```
Optional
The type setting is used to explicitly set a code block as input or output. By default, snippet-compiler-markdown-render
assumes that the first code block with a given tag is the input (the snippet to compile) and the following code blocks with the
same tag are output (the result of compiling and/or running the snippet). So, if the output needs to preceded the input,
you can set the type explicitly.
The standard library functions and classes live in the `std::` namespace. If you see this error,
<!---
tag : example-1
type : out
-->
```bash
This will be replaced with the compiler output, which will fail because cout is called without 'std::'
```
it's probably because you forgot to prefix `cout` with `std::`, like this
<!---
tag : example-1
type : in
-->
```cpp
#include<iostream>
int main() {
cout << "Hello World";
}
```
You will get an error like this
If the file
setting is given, then the code block will be filled with the contents of the given file. It is used to just insert
the contents of a file into a code block.
<!---
file: example.cpp
-->
```cpp
This will be replaced with the contents of example.cpp
```
The cmd
setting is similar to file
except that it runs the specified command and replaces the code block with its output.
It goes without saying, but: DON'T RENDER UNTRUSTED MARKDOWN FILES.
<!---
cmd: echo "hello world"
-->
```bash
This will be replaced with, well, you know...
```
wd
sets the working directory for the cwd
command. This is useful if you want to run a command in a subdirectory.
<!---
cmd: pwd
wd: subdir
-->
```bash
This will be replaced with /path/to/current/file/subdir
```
The snippet-compiler
setting is used to pass options and flags to the snippet-compiler
command. Any option or flag that
can be passed to the command can be given here. Options are listed under the options
dict, flags are listed in the flags
list.
<!---
tag : example
snippet-compiler:
flags :
- run # run the executable after it is compiled
options :
exec-name : a.out
compiler-command : 'g++-10 -std=c++20 {file}' # don't forget the {file} placeholder
-->
This program
```cpp
#include <iostream>
int main(){std::cout << "hello world!";}
```
outputs this
```bash
hello world!
```