• Stars
    star
    124
  • Rank 278,271 (Top 6 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 5 years ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Asciidoctor.js extension to convert diagrams to images using Kroki!

๐Ÿ– Asciidoctor Kroki Extension

Build JavaScript Build JavaScript npm version Gem version Zulip Chat

An extension for Asciidoctor.js to convert diagrams to images using Kroki!

Install

Node.js

Install the dependencies:

$ npm i asciidoctor asciidoctor-kroki

Create a file named kroki.js with following content and run it:

const asciidoctor = require('@asciidoctor/core')()
const kroki = require('asciidoctor-kroki')

const input = 'plantuml::hello.puml[svg,role=sequence]'

kroki.register(asciidoctor.Extensions) // <1>
console.log(asciidoctor.convert(input, { safe: 'safe' }))

const registry = asciidoctor.Extensions.create()
kroki.register(registry) // <2>
console.log(asciidoctor.convert(input, { safe: 'safe', extension_registry: registry }))

<1> Register the extension in the global registry
<2> Register the extension in a dedicated registry

Browser

Install the dependencies:

$ npm i asciidoctor asciidoctor-kroki

Create a file named kroki.html with the following content and open it in your browser:

<html lang="en">
  <head>
    <title>Asciidoctor x Kroki</title>
    <meta charset="utf-8">
    <script src="node_modules/@asciidoctor/core/dist/browser/asciidoctor.js"></script>
    <script src="node_modules/asciidoctor-kroki/dist/browser/asciidoctor-kroki.js"></script>
  </head>
  <body>
    <div id="content"></div>
    <script>
      const input = `Let's take an example with a _GraphViz_ "Hello World":

[graphviz]
....
digraph G {
  Hello->World
}
....`

      const asciidoctor = Asciidoctor()

      const registry = asciidoctor.Extensions.create()
      AsciidoctorKroki.register(registry) // <1>
      const result = asciidoctor.convert(input, { safe: 'safe', extension_registry: registry })
      document.getElementById('content').innerHTML = result
    </script>
  </body>
</html>

<1> Register the extension in a dedicated registry

โ— IMPORTANT: If you want to reference a diagram file in a browser environment you will need to define the base directory using the base_dir option. In addition, you will also need to provide an implementation to read a binary file synchronously for a given path. You can find an implementation based on XMLHttpRequest in the source code:

const httpGet = (uri, encoding = 'utf8') => {
let data = ''
let status = -1
try {
const xhr = new XMLHttpRequest()
xhr.open('GET', uri, false)
if (encoding === 'binary') {
xhr.responseType = 'arraybuffer'
}
xhr.addEventListener('load', function () {
status = this.status
if (status === 200 || status === 0) {
if (encoding === 'binary') {
const arrayBuffer = xhr.response
const byteArray = new Uint8Array(arrayBuffer)
for (let i = 0; i < byteArray.byteLength; i++) {
data += String.fromCharCode(byteArray[i])
}
} else {
data = this.responseText
}
}
})
xhr.send()
} catch (e) {
throw new Error(`Error reading file: ${uri}; reason: ${e.message}`)
}
// assume that no data means it doesn't exist
if (status === 404 || !data) {
throw new Error(`No such file: ${uri}`)
}
return data
}
. Once httpGet is defined, here's how we should configure the extension:

const registry = asciidoctor.Extensions.create()
AsciidoctorKroki.register(registry, {
  vfs: {
    read: (path, encoding = 'utf8') => httpGet(path, encoding),
    exists: (_) => false,
    add: (_) => { /* no-op */ }
  }
})
const input = 'plantuml::hello.puml[svg,role=sequence]'
asciidoctor.convert(input, { safe: 'safe', base_dir: window.location.origin, extension_registry: registry })

Ruby

Install the dependency:

$ gem install asciidoctor-kroki

Require the library using the --require (or -r) option from the Asciidoctor CLI:

$ asciidoctor -r asciidoctor-kroki doc.adoc

Antora Integration

If you are using Antora, you can integrate Kroki in your documentation site.

  1. Install the extension in your playbook project:

    $ npm i asciidoctor-kroki
    
  2. Register the extension in your playbook file:

    asciidoc:
      extensions:
        - asciidoctor-kroki

    https://docs.antora.org/antora/2.3/playbook/configure-asciidoc/#extensions

  3. Enjoy!

๐Ÿ’ก TIP: You can use the kroki-fetch-diagram option to download the images from Kroki at build time. In other words, while viewing pages you won't rely on Kroki anymore. However, in Antora, this is not currently compatible with inline SVG images.

asciidoc:
  attributes:
    kroki-fetch-diagram: true

Usage

In your AsciiDoc document, you can either write your diagram inline or, alternatively, you can make a reference to the diagram file using macro form or with the include directive.

Here's an example where we declare a GraphViz diagram directly in our AsciiDoc document using the block syntax:

[graphviz]
....
digraph foo {
  node [style=rounded]
  node1 [shape=box]
  node2 [fillcolor=yellow, style="rounded,filled", shape=diamond]
  node3 [shape=record, label="{ a | b | c }"]

  node1 -> node2 -> node3
}
....

GraphViz diagram

In the example below, we are using the vegalite macro to reference a file named chart.vlite:

vegalite::chart.vlite[svg,role=chart,opts=interactive]

Vega-Lite chart diagram

Finally, we can use the include directive to reference a diagram file:

[plantuml,alice-bob,svg,role=sequence]
....
include::alice-bob.puml[]
....

PlantUML diagram

References and includes with Antora

If you use this Asciidoctor Kroki Extension in combination with Antora, all references and includes MUST use Antora Resource IDs. The .puml-files are best placed in the partials-directory of the modules.

Block macros

vegalite::partial$chart.vlite[svg,role=chart,opts=interactive]

Includes

[plantuml,alice-bob,svg,role=sequence]
----
include::partial$alice-bob.puml[]
----

Syntax

You can declare positional and named attributes when using the block or the macro form.

Positional attributes

When using the block form:

  1. The first positional attribute specifies the diagram type (see below for a complete list).
  2. The second optional positional attribute assigns a file name (i.e. target) to the generated diagram. Currently, the value of this attribute is ignored, and an auto-generated hash will be used as file name for caching purposes (see #48).
  3. The third optional positional attribute specifies the image format.

Example:

[mermaid,abcd-flowchart,svg]
....
graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;
....

In the above example, the diagram type is mermaid, the file name (i.e. target) is abcd-flowchart, and the image format is svg.

When using the macro form:

  1. The first optional positional attribute specifies the image format.

Example:

vegalite::chart.vlite[svg]

In the above example, the diagram type is vegalite, the target is chart.vlite, and the image format is svg.

Named attributes

You can also use both positional and named attributes. Here's an example:

.PlantUML example
[plantuml#diagAliceBob,alice-and-bob,svg,role=sequence]
....
alice -> bob
....

As you can see, we specified an id using the syntax #diagAliceBob just after the diagram type, and we are also using a named attribute to assign a role using role=sequence.

Here's another example using the macro form:

vegalite::chart.vlite[svg,role=chart,opts=interactive]

We are using a positional attribute to declare the image format and two named attributes to define the role and options.

Attributes

It's important to note that not all attributes are used in all converters. Here's a list of all attributes used in the built-in HTML 5 converter:

  • target
  • width
  • height
  • format (default svg)
  • fallback
  • link
  • float
  • align
  • role
  • title (used to define an alternative text description of the diagram)

Options

In addition, the following options are available when using the SVG format:

  • inline
  • interactive
  • none (used for cancelling defaults)

Options can be defined using options attribute (or opts for short):

[blockdiag,opts=inline]
....
blockdiag {
  Kroki -> generates -> "Block diagrams";

  Kroki [color = "greenyellow"];
  "Block diagrams" [color = "pink"];
}
....

Supported diagram types

Kroki currently supports the following diagram libraries:

Each diagram libraries support one or more output formats. Consult the Kroki documentation to find out which formats are supported.

Configuration

Attribute name Description Default value
kroki-server-url The URL of the Kroki server (see "Using Your Own Kroki") https://kroki.io
kroki-fetch-diagram Define if we should download (and save on the disk) the images from the Kroki server.
This feature is not available when running in the browser.
false
kroki-http-method Define how we should get the image from the Kroki server. Possible values:
  • get: always use GET requests
  • post: always use POST requests
  • adaptive: use a POST request if the URI length is longer than kroki-max-uri-length (default 4000) characters, otherwise use a GET request
adaptive
kroki-plantuml-include A file that will be included at the top of all PlantUML diagrams as if !include file was used. This can be useful when you want to define a common skin for all your diagrams. The value can be a path or a URL.
kroki-plantuml-include-paths Search path(s) that will be used to resolve !include file additionally to current diagram directory, similar to PlantUML property plantuml.include.path. Please use directory delimiter ; (Windows) or : (Unix) for multiple paths, e.g.: "c:/docu/styles;c:/docu/library" or "~/docu/styles:~/docu/library"
kroki-max-uri-length Define the max URI length before using a POST request when using adaptive HTTP method (kroki-http-method) 4000

โ— IMPORTANT: kroki-fetch-diagram and kroki-plantuml-include are only available when safe mode is server or lower. If you want to learn more about Asciidoctor safe modes: https://docs.asciidoctor.org/asciidoctor/latest/safe-modes/

Default configuration

By default, images are generated as SVG when possible. To alter this, set the kroki-default-format attribute:

:kroki-default-format: png

You can unset this with :kroki-default-format!: or :kroki-default-format: svg.

โ„น๏ธ NOTE: An AsciiDoc attribute can be defined through the CLI or API, in the documentโ€™s header or in the documentโ€™s body. In addition, if you are using Antora, you can define AsciiDoc attributes in your playbook and/or in your component descriptor.

References:

For instance, in an Antora playbook or component descriptor:

asciidoc:
  attributes:
    kroki-default-format: png@

(the @ allows the attribute value to be reset in documents)

By default, Asciidoctor Kroki generates a link, to a Kroki server or a local file. To change the default for SVG diagrams, set the kroki-default-options attribute.

:kroki-default-options: inline

You can unset this with :kroki-default-options: none or :kroki-default-options!: or specify opts=none in a block or macro.

Preprocessing

Some diagram libraries allow referencing external entities by URL or accessing resources from the filesystem. For example PlantUML allows the !import directive to pull fragments from the filesystem or a remote URL or the standard library. Similarly, Vega-Lite can load data from a URL using the url property.

By default, the Kroki server is running in SECURE mode which restrict access to files on the file system and on the network.

For ease of use and convenience, Asciidoctor Kroki will try to resolve and load external resources before sending a request to the Kroki server. This feature is only available when Asciidoctor safe mode is server or lower.

Using Your Own Kroki

By default, this extension sends information and receives diagrams back from https://kroki.io.

You may choose to use your own server due to:

  • Network restrictions - if Kroki is not available behind your corporate firewall
  • Network latency - you are far from the European public instance
  • Privacy - you don't want to send your diagrams to a remote server on the internet

This is done using the kroki-server-url attribute. Typically, this is at the top of the document (under the title):

:kroki-server-url: http://my-server-url:port

For instance, if you have followed the instructions to set up a self-managed server using Docker you can use the following:

:kroki-server-url: http://localhost:8080

Note that either the http:// or https:// prefix is required (the default Docker image only uses http).

You can also set this attribute using the Javascript API, for instance:

asciidoctor.convertFile('file.adoc', { safe: 'safe', attributes: { 'kroki-server-url': 'http://my-server-url:port' } })

Contributing

Setup

To build this project, you will need the latest active LTS of Node.js and npm (we recommend nvm to manage multiple active Node.js versions).

The current latest Node LTS version is: v14.15.x

Please use latest npm version v7.x to generate lockfile using v2 format (i.e., "lockfileVersion": 2), see lockfileversion

Update NPM @ Linux

npm i -g npm

Update NPM @ Windows

  1. Open PowerShell as Administrator selecting Run as Administrator

  2. Install npm-windows-upgrade

     Set-ExecutionPolicy Unrestricted -Scope CurrentUser -Force
     npm install --global --production npm-windows-upgrade
    
  3. Upgrade npm

     npm-windows-upgrade
    

Reference: npm-windows-upgrade

Building

  1. Install the dependencies:

    $ npm i
    
  2. Generate a distribution:

    $ npm run dist
    

When working on a new feature or when fixing a bug, make sure to run the linter and the tests suite:

$ npm run lint
$ npm run test

More Repositories

1

asciidoctor

๐Ÿ’Ž A fast, open source text processor and publishing toolchain, written in Ruby, for converting AsciiDoc content to HTML 5, DocBook 5, and other formats.
Ruby
4,437
star
2

asciidoctor-pdf

๐Ÿ“ƒ Asciidoctor PDF: A native PDF converter for AsciiDoc based on Asciidoctor and Prawn, written entirely in Ruby.
Ruby
1,089
star
3

asciidoctor.js

๐Ÿ“œ A JavaScript port of Asciidoctor, a modern implementation of AsciiDoc
JavaScript
664
star
4

asciidoctorj

โ˜• Java bindings for Asciidoctor. Asciidoctor on the JVM!
Java
593
star
5

asciidoctor-diagram

โ†”๏ธ Asciidoctor diagram extension, with support for AsciiToSVG, BlockDiag (BlockDiag, SeqDiag, ActDiag, NwDiag), Ditaa, Erd, GraphViz, Mermaid, Msc, PlantUML, Shaape, SvgBob, Syntrax, UMLet, Vega, Vega-Lite and WaveDrom.
Ruby
419
star
6

asciidoctor-intellij-plugin

AsciiDoc plugin for products on the IntelliJ platform (IDEA, RubyMine, etc)
Java
324
star
7

asciidoctor.org

๐ŸŒ Asciidoctor project site. Composed in AsciiDoc. Baked with Awestruct.
SCSS
308
star
8

asciidoctor-maven-plugin

A Maven plugin that uses Asciidoctor via JRuby to process AsciiDoc source files within the project.
Java
298
star
9

jekyll-asciidoc

๐Ÿ’‰ A Jekyll plugin that converts AsciiDoc source files in your site to HTML pages using Asciidoctor.
Ruby
298
star
10

docker-asciidoctor

๐Ÿšข A Docker image for using the Asciidoctor toolchain to process AsciiDoc content
Shell
294
star
11

asciidoctor-vscode

AsciiDoc support for Visual Studio Code using Asciidoctor
TypeScript
281
star
12

asciidoctor-gradle-plugin

A Gradle plugin that uses Asciidoctor via JRuby to process AsciiDoc source files within the project.
Groovy
272
star
13

asciidoctor-reveal.js

๐Ÿ”ฎ A reveal.js converter for Asciidoctor and Asciidoctor.js. Write your slides in AsciiDoc!
HTML
270
star
14

asciidoctor-epub3

๐Ÿ“˜ Asciidoctor EPUB3 is a set of Asciidoctor extensions for converting AsciiDoc to EPUB3 & KF8/MOBI
Ruby
203
star
15

asciidoctor-browser-extension

โšช An extension for web browsers that converts AsciiDoc files to HTML using Asciidoctor.js.
CSS
202
star
16

asciidoctor-maven-examples

A collection of example projects that demonstrates how to use the Asciidoctor Maven plugin.
Java
187
star
17

asciidoctor-stylesheet-factory

!DEPRECATED! This was the utility project for producing the default stylesheet for the HTML converter in Asciidoctor. The source of the default stylesheet now lives in the main Asciidoctor repository.
SCSS
178
star
18

kramdown-asciidoc

A kramdown extension for converting Markdown documents to AsciiDoc.
Ruby
174
star
19

asciidoctor-gradle-examples

A collection of example projects that demonstrates how to use the Asciidoctor Gradle plugin
Java
145
star
20

atom-asciidoc-preview

โš› AsciiDoc preview for the Atom editor.
CoffeeScript
142
star
21

asciidoclet

๐Ÿ“‹ A Javadoc Doclet based on Asciidoctor that lets you write Javadoc in the AsciiDoc syntax.
Java
120
star
22

asciidoctor-fopub

A portable DocBook-to-PDF build command that wraps DocBook XSL and Apache FOP
Java
111
star
23

asciidoctor-latex

๐Ÿ“ Add LaTeX features to AsciiDoc & convert AsciiDoc to LaTeX
Ruby
106
star
24

jekyll-asciidoc-quickstart

A template project for creating AsciiDoc-based websites using Jekyll.
CSS
105
star
25

asciidoctor-extensions-lab

A lab for testing and demonstrating Asciidoctor extensions. Please do not use this code in production. If you want to use one of these extensions in your application, create a new project, import the code, and distribute it as a RubyGem. You can then request to make it a top-level project under the Asciidoctor organization.
Ruby
97
star
26

asciidoctor-confluence

Push Asciidoctor file to Confluence
Ruby
80
star
27

asciidoctor-backends

Backends (i.e., templates) for Asciidoctor, a Ruby port of AsciiDoc.
HTML
66
star
28

asciidoctor-bibtex

Add bibtex citation support for asciidoc documents
Ruby
59
star
29

docgist

Render AsciiDoc documents from Gists, GitHub, DropBox and other remote sources in the browser.
CSS
56
star
30

asciidoc-docs

The source files in this repository served as the initial contribution for the AsciiDoc Language specification project at Eclipse. This repository is now archived.
55
star
31

brackets-asciidoc-preview

Live Preview of AsciiDoc for Adobe Brackets
JavaScript
51
star
32

asciidoctor-bespoke

๐Ÿ…ฑ๏ธ An Asciidoctor converter that generates the HTML component of a Bespoke.js presentation from AsciiDoc.
Slim
49
star
33

sublimetext-asciidoc

AsciiDoc Package for SublimeText 3
Python
49
star
34

asciidoctor-mathematical

An extension for Asciidoctor that converts the content of STEM blocks and inline macros using Mathematical.
Ruby
44
star
35

atom-language-asciidoc

โš› AsciiDoc language package for the Atom editor.
CoffeeScript
42
star
36

asciidoctorj-pdf

AsciidoctorJ PDF bundles the Asciidoctor PDF RubyGem (asciidoctor-pdf) so it can be loaded into the JVM using JRuby.
Java
33
star
37

asciidoctorj-screenshot

A set of AsciidoctorJ extensions for adding automated screenshots to an AsciiDoc document.
Groovy
32
star
38

asciidoctor-firefox-addon

๐Ÿบ An add-on for Mozilla Firefox that converts AsciiDoc files to HTML directly in the browser using Asciidoctor.js.
JavaScript
32
star
39

asciidoctor-tabs

An extension for Asciidoctor that adds a tabs block to the AsciiDoc syntax.
Ruby
29
star
40

asciidoctor-lein-plugin

A Leiningen plugin for generating documentation using Asciidoctor
Clojure
26
star
41

asciidoctor-chart

A set of Asciidoctor extensions that add a chart block and block macro to AsciiDoc for including charts in your AsciiDoc document.
Ruby
25
star
42

asciidoctor-reducer

โš—๏ธ A tool to generate a single AsciiDoc document by expanding all the include directives reachable from the parent document.
Ruby
24
star
43

asciimath

Asciimath parser
Ruby
23
star
44

docbookrx

(An early version of) a DocBook to AsciiDoc converter written in Ruby.
Ruby
22
star
45

gitbucket-asciidoctor-plugin

A GitBucket plug-in that provided AsciiDoc rendering capabilities
Scala
18
star
46

asciidoctor-leanpub-converter

A backend for AsciidoctorJ to generate Leanpub-flavoured Markdown
Groovy
16
star
47

codemirror-asciidoc

AsciiDoc mode for CodeMirror
JavaScript
16
star
48

asciidoc-grammar-prototype

โ›” ARCHIVED: An experiment to create of a formal grammar for the AsciiDoc markup language. Work is being continued at https://github.com/Mogztter/asciidoctor-inline-parser.
Java
16
star
49

asciidoctor-cli.js

The Command Line Interface (CLI) for Asciidoctor.js
JavaScript
15
star
50

asciidoctor-documentation-planning

โ›” ARCHIVED: Planning for the documentation that covers the AsciiDoc syntax, the Asciidoctor processor, and various projects in the Asciidoctor ecosystem.
15
star
51

asciidoctorj-groovy-dsl

A Groovy DSL that allows for easy definition of Asciidoctor extensions
Groovy
14
star
52

gulp-asciidoctor

gulp-asciidoctor
JavaScript
14
star
53

asciidoctor-docs-ui

The project that produces the UI (theme & user interactions) for docs.asciidoctor.org.
CSS
12
star
54

docs.asciidoctor.org

The Antora playbook project (i.e., site manifest) for the Asciidoctor documentation site.
JavaScript
11
star
55

asciidoctor-deck.js

โ›” ARCHIVED: deck.js converter templates for Asciidoctor, implemented in Haml
HTML
11
star
56

asciidoctor-doctest

๐Ÿ”จ Test suite for Asciidoctor backends
Ruby
10
star
57

asciidoctorj-reveal.js

AsciidoctorJ Reveal.js bundles the Asciidoctor Reveal.js RubyGem (asciidoctor-revealjs) so it can be loaded into the JVM using JRuby
Java
10
star
58

asciidoctor-ant

๐Ÿœ Ant task to render Asciidoc documentation
Ruby
9
star
59

asciidoctor-diagram-java

Java
9
star
60

asciidoctorj-diagram

AsciidoctorJ Diagram bundles the Asciidoctor Diagram RubyGem (asciidoctor-diagram) so it can be loaded into the JVM using JRuby.
Java
9
star
61

atom-asciidoc-image-helper

โš› Tool to make insertion of images into AsciiDocs easier in the Atom editor.
CoffeeScript
9
star
62

atom-asciidoc-assistant

โš› AsciiDoc Assistant package for the Atom editor.
Shell
9
star
63

asciidoctor-chrome-editor

โ›” ARCHIVED: AsciiDoc Editor inside Chrome!
JavaScript
8
star
64

guard-asciidoc

Guard::AsciiDoc monitors and automatically renders your AsciiDoc documents using Asciidoctor
Ruby
8
star
65

atom-autocomplete-asciidoc

โš› AsciiDoc language autocompletions
CoffeeScript
7
star
66

asciidoctor-docbook.js

DocBook converter for Asciidoctor.js
JavaScript
7
star
67

asciidoctor-mallard

A Mallard 1.0 converter for Asciidoctor
Ruby
6
star
68

asciidoctor-lazybones

Lazybones templates for Asciidoctor projects
Groovy
5
star
69

asciidoctor-fb2

๐Ÿ“• Asciidoctor FB2 is an Asciidoctor extension for converting AsciiDoc to FB2
Ruby
5
star
70

html-pipeline-asciidoc_filter

โ›” ARCHIVED: An AsciiDoc processing filter for html-pipeline based on Asciidoctor.
Ruby
5
star
71

brand

Brand assets and visual identity for the Asciidoctor project
Shell
4
star
72

opal-node-runtime

โšก๏ธ Opal Runtime specifically designed for Asciidoctor
JavaScript
4
star
73

asciidoctor-template.js

โ›”๏ธ DEPRECATED: Generic template backend for Asciidoctor.js
JavaScript
4
star
74

asciidoctor-deb-mirror

A mirror of the asciidoctor deb (Debian) package build in the pkg-ruby-extras package group. DO NOT PUSH CHANGES TO THIS MIRROR.
Ruby
3
star
75

asciidoctor-community-docs

Process, policy, and other shared documentation for the Asciidoctor community of projects.
3
star
76

docker-asciidoctorj

Ensure that AsciidoctorJ can be used by apps into a Java Application Server (WildFly AS for now)
Java
3
star
77

asciidoctorj-chart

AsciidoctorJ Chart bundles the Asciidoctor Chart RubyGem (asciidoctor-chart) so it can be loaded into the JVM using JRuby.
HTML
3
star
78

asciidoctor-grunt-plugin

A Grunt plugin that uses Asciidoctor via Asciidoctor.js to process AsciiDoc source files within the project.
HTML
2
star
79

asciidoctor-rpm-mirror

A mirror of the package spec for the rubygem-asciidoctor (alias: asciidoctor) rpm. DO NOT PUSH CHANGES TO THIS MIRROR.
Ruby
2
star
80

default-to-asciidoc-chrome-extension

You love AsciiDoc and you want it to be the default option, this extension is for you!
JavaScript
2
star
81

asciidoctorj-epub3

AsciidoctorJ EPUB3 bundles the Asciidoctor EPUB3 RubyGem (asciidoctor-epub3) so it can be loaded into the JVM using JRuby.
Java
1
star
82

asciidoctor-docbook45

!DEPRECATED! An Asciidoctor converter that converts AsciiDoc to DocBook 4.5. This converter is community maintained and will not be released. Use the built-in DocBook 5 converter instead.
Ruby
1
star