Doku is a framework for building documentation with code-as-data methodology in mind.

Related tags

Utilities doku
Overview

Doku   crates-badge

Doku is a framework for building documentation with code-as-data methodology in mind.

Say goodbye to stale, hand-written documentation - with Doku, code is the documentation!

Made by Anixe

Examples

, /// Person's favourite color favorite_color: Color, } #[derive(Doku)] enum Color { #[doku(rename = "red-uwu")] Red, #[doku(rename = "green-uwu")] Green, #[doku(rename = "bluwu")] Blue, } fn main() { println!("{}", doku::to_json::()); } ">
// doku/examples/doku.rs

use doku::prelude::*;

type People = Vec;

#[derive(Doku)]
struct Person {
    /// Person's first name
    #[doku(example = "Janet")]
    name: String,

    /// Person's last name
    #[doku(example = "NotARobot")]
    surname: Option<String>,

    /// Person's favourite color
    favorite_color: Color,
}

#[derive(Doku)]
enum Color {
    #[doku(rename = "red-uwu")]
    Red,

    #[doku(rename = "green-uwu")]
    Green,

    #[doku(rename = "bluwu")]
    Blue,
}

fn main() {
    println!("{}", doku::to_json::());
}
// cd doku && cargo run --example doku

[
  {
    "name": "Janet",             // Person's first name
    "surname": "NotARobot",      // Person's last name; optional
    "favorite_color": "red-uwu"  // Person's favourite color; possible variants:
                                 // - "red-uwu" = Red
                                 // - "green-uwu" = Green
                                 // - "bluwu" = Blue
  },
  /* ... */
]

If you use Serde, then you'll be pleasantly surprised by hearing that Doku understands (the most common) Serde attributes; just sprinkle #[derive(Doku)] and get them docs for free!

, } #[derive(Serialize, Doku)] #[serde(transparent)] struct PaginationWrapper(Pagination); #[derive(Serialize, Doku)] struct Pagination { current_page: usize, last_page: usize, } #[derive(Serialize, Doku)] struct User { #[doku(example = "alan.turing")] // (explicit examples are optional) login: String, #[doku(example = "lofi hip hop radio")] favorite_radio: String, } fn main() { println!("{}", doku::to_json::()); } ">
// doku/examples/serde.rs

use doku::prelude::*;
use serde::Serialize;

#[derive(Serialize, Doku)]
struct Response {
    #[serde(flatten)]
    pagination: PaginationWrapper,

    #[serde(rename = "items")]
    users: Vec<User>,
}

#[derive(Serialize, Doku)]
#[serde(transparent)]
struct PaginationWrapper(Pagination);

#[derive(Serialize, Doku)]
struct Pagination {
    current_page: usize,
    last_page:    usize,
}

#[derive(Serialize, Doku)]
struct User {
    #[doku(example = "alan.turing")] // (explicit examples are optional)
    login: String,

    #[doku(example = "lofi hip hop radio")]
    favorite_radio: String,
}

fn main() {
    println!("{}", doku::to_json::());
}
// cd doku && cargo run --example serde

{
  "current_page": 123,
  "last_page": 123,
  "items": [
    {
      "login": "alan.turing",
      "favorite_radio": "lofi hip hop radio"
    },
    /* ... */
  ]
}

(you'll find more in ./doku/examples.)

Getting started

[dependencies]
doku = "0.9.0"

Doku has been made with Serde & serde_json in mind, so if you use them, starting with Doku is as easy as adding #[derive(Doku)] to your types and invoking doku::to_json::().

(there's also doku::JsonPrinter::new(), if you want to have more control over the output's format.)

Somewhat obviously, Doku cannot introspect any hand-written (de)serializers, so if you use them, you'll have to inform Doku on how those manually-(de)serialized types look like (see ./doku/examples/custom-impl.rs) - more often than not, the derive-macro should suffice, though.

Using #[doku]

The #[doku] attribute allows to provide additional information about a type:

#[derive(Doku)]
struct User {
    /// `example=` provides an example value for a field.
    ///
    /// Doku has a list of predefined examples for standard types (e.g. floats
    /// get documented as `123.45`), and you can use this attribute to provide
    /// a more meaningful example.
    ///
    /// Each field can have at most one example.
    #[doku(example = "alan.turing")]
    login: String,
    
    /// `as=` overrides which type this field gets documented as.
    ///
    /// This is useful when you e.g. depend on another crate that doesn't use
    /// Doku, and for which - due to Rust's orphan rules - you cannot impl
    /// `doku::TypeProvider` by hand.
    ///
    /// This is similar to `#[serde(remote = ...)]`.
    ///
    /// This doesn't affect Serde's (de)serialization mechanism.
    #[doku(as = "String")]
    favourite_terminal: iso_terminal_list::Terminal,

    /// `skip` allows to ignore given field in the documentation.
    ///
    /// Skipped fields don't have to have `impl doku::TypeProvider`, so this is
    /// kind of a get-out-of-jail-free card if you can live with this field 
    /// missing from the docs.
    ///
    /// This doesn't affect Serde's (de)serialization mechanism.
    #[doku(skip)]
    favourite_color: iso_color_list::Color,

    /// Last but not least, the `#[doku]` attribute allows to overwrite most of
    /// Serde's attributes for the purposes of documentation.
    ///
    /// E.g. this will cause for the type to be serialized with this field
    /// flattened, but the documentation will keep the field as-is, unflattened.
    #[serde(flatten)]
    #[doku(flatten = false)]
    overriden_flatten: Foo,
    
    /// cont'd - this field will not be (de)serialized by Serde, but will be
    /// present in the documentation.
    #[serde(skip)]
    #[doku(skip = false)]
    overriden_skip: bool,
}

Can I use it without Serde?

Yes, totally! Doku merely understands Serde annotations, but doesn't require any of them (see: the very first example in this readme).

How does it work?

When you wrap a type with #[derive(Doku)]:

#[derive(Doku)]
struct User {
    /// Who? Who?
    #[doku(example = "alan.turing")]
    login: String,
}

... the derive-macro generates an impl doku::TypeProvider for ...:

impl doku::TypeProvider for User {
    fn ty() -> doku::Type {
        let login = doku::Field {
            ty: doku::Type {
                comment: Some("Who? Who?"),
                example: Some("alan.turing"),
                ..String::ty()
            },
            flattened: false,
        };

        doku::Type::from_def(doku::TypeDef::Struct {
            fields: doku::Fields::Named {
                fields: vec![
                    ("login", login)
                ],
            },
            transparent: false,
        })
    }
}

... and later, when you invoke doku::to_json(), it just calls this fn ty() method:

fn to_json() -> String {
    let ty = Ty::ty();
    
    match &ty.def {
        doku::TypeDef::String => print_string(/* ... */),
        doku::TypeDef::Struct { fields, .. } => print_struct(/* ... */),
        /* ... */
    }
}

There's no magic, no RTTI-related hacks, no unsafety - just Rust being awesome.

Also, all those doku::* types are public - if you wanted, you could write your own visitor that generates Doku-powered documentation for your own output format, not necessarily JSON!

Quick help

trait TypeProvider is not implemented for ...

The offending type (enum / struct) is missing the #[derive(Doku)]:

#[derive(Doku)]
struct Foo {
    bar: Bar, // trait `TypeProvider` is not implemented for `Bar`
}

struct Bar {
    /* ... */
}

Compatibility

Legend:

  • = not supported (the derive-macro will return an error)
  • = supported
  • + no-op = supported, but ignored (i.e. doesn't affect the documentation)

#[serde] for containers

  • #[serde(rename = "...")]
  • #[serde(rename(serialize = "..."))]
  • #[serde(rename(deserialize = "..."))]
  • #[serde(rename(serialize = "...", deserialize = "..."))]
  • #[serde(rename_all = "...")]
  • #[serde(rename_all(serialize = "..."))]
  • #[serde(rename_all(deserialize = "..."))]
  • #[serde(rename_all(serialize = "...", deserialize = "..."))]
  • #[serde(deny_unknown_fields)] (no-op)
  • #[serde(tag = "...")]
  • #[serde(tag = "...", content = "...")]
  • #[serde(untagged)]
  • #[serde(bound = "...")]
  • #[serde(bound(serialize = "..."))]
  • #[serde(bound(deserialize = "..."))]
  • #[serde(bound(serialize = "...", deserialize = "..."))]
  • #[serde(default)] (no-op)
  • #[serde(default = "...")] (no-op)
  • #[serde(remote = "...")]
  • #[serde(transparent)]
  • #[serde(from = "...")]
  • #[serde(try_from = "...")]
  • #[serde(into = "...")]
  • #[serde(crate = "...")] (no-op)

#[serde] for variants

  • #[serde(rename = "...")]
  • #[serde(rename(serialize = "..."))]
  • #[serde(rename(deserialize = "..."))]
  • #[serde(rename(serialize = "...", deserialize = "..."))]
  • #[serde(alias = "...")]
  • #[serde(rename_all = "...")]
  • #[serde(skip)]
  • #[serde(skip_serializing)]
  • #[serde(skip_deserializing)]
  • #[serde(serialize_with = "...")] (no-op)
  • #[serde(deserialize_with = "...")] (no-op)
  • #[serde(with = "...")] (no-op)
  • #[serde(bound = "...")]
  • #[serde(borrow)]
  • #[serde(borrow = "...")]
  • #[serde(other)] (no-op)

#[serde] for fields

  • #[serde(rename = "...")]
  • #[serde(rename(serialize = "..."))]
  • #[serde(rename(deserialize = "..."))]
  • #[serde(rename(serialize = "...", deserialize = "..."))]
  • #[serde(alias = "...")]
  • #[serde(default)] (no-op)
  • #[serde(default = "...'")] (no-op)
  • #[serde(skip)]
  • #[serde(skip_serializing)]
  • #[serde(skip_deserializing)]
  • #[serde(skip_serializing_if = "...")] (no-op)
  • #[serde(serialize_with = "...")] (no-op)
  • #[serde(deserialize_with = "...")] (no-op)
  • #[serde(with = "...")] (no-op)
  • #[serde(borrow)] (no-op)
  • #[serde(borrow = "...")] (no-op)
  • #[serde(getter = "...")]

Miscellaneous

  • recursive types
  • generic types (you can write impl TypeProvider by hand though)

Contributing

Found a bug, or something doesn't work as expected? Please let us know on GitHub; patches are welcome, too!

If you want to try hacking on Doku, the entry points are:

We've got integration tests inside doku/tests, and creating a new one is as easy as adding its path to the json.rs file, copy-pasting some another example there and adjusting the code.rs file. From within the doku directory, you can run tests using cargo test, and you can adjust the expected-files automatically using make bless (requires make and fd).

Comments
  • Compatibility with smart-default crate

    Compatibility with smart-default crate

    First of all, thanks for this crate, its really useful!

    I would like to use it to generate documentation for our config file. As you can see, this file has default values configured for most fields using the smart-default crate. It would be very nice if doku could use these defaults when generating its output.

    C-enhancement A-macro A-printer A-object 
    opened by Nutomic 7
  • Trait doku::Document is not implemented, but example string is given

    Trait doku::Document is not implemented, but example string is given

    I have the following field:

      #[default(Url::parse("http://pictrs:8080").unwrap())]
      #[doku(example = "http://pictrs:8080")]
      pub url: Url,
    

    It fails to comopile with the trait doku::Document is not implemented for url::Url. I would expect this to work, by doku using the explicitly provided example, which is already a string. Alternatively, Url implements ToString, so that could also be used with the default value.

    opened by Nutomic 6
  • `to_json_val` results in type information instead of value when using `skip_serializing`

    `to_json_val` results in type information instead of value when using `skip_serializing`

    Hi,

    when creating a JSON representation of an initialized struct via doku::to_json_val(&Config::default());, fields that are marked with #[serde(skip_serializing)] are set to their respective type and not, as expected, to the corresponding value.

    use doku::Document;
    use serde::Serialize;
    
    #[derive(Serialize, Document)]
    struct Config {
        /// Database's host
        db_host: String,
        #[serde(skip_serializing)]
        skip_ser: String,
    }
    
    impl Default for Config {
        fn default() -> Self {
            Self {
                db_host: "localhost".to_string(),
                skip_ser: "skip_ser".to_string(),
            }
        }
    }
    
    fn main() {
        let doc = doku::to_json_val(&Config::default());
        // passes
        assert_eq!(
            r#"{
      // Database's host
      "db_host": "localhost",
      "skip_ser": "string"
    }"#,
            doc
        );
    
        // fails
        assert_eq!(
            r#"{
      // Database's host
      "db_host": "localhost",
      "skip_ser": "skip_ser"
    }"#,
            doc
        );
    }
    
    opened by arctic-alpaca 4
  • Option to exclude field

    Option to exclude field

    We have a config field that is only for debugging, and shouldnt be set in production. For that reason, it also shouldnt be included in the default config generated by doku. But I dont see any way to achieve that, even with #[default(None)] and no doku annotation its included in the output. Maybe the field could be skipped in that case, or there could be a separate #doku(skip) annotation.

    You can see the code in this PR.

    opened by Nutomic 2
  • Hidden `#[doku(` in docs.rs example snippet

    Hidden `#[doku(` in docs.rs example snippet

    Minor thing but the example on https://docs.rs/doku/0.10.1/doku/ does not include the lines like #[doku(example = "localhost")] which made the example quite hard to understand! They appear correctly in the README on Github

    opened by dbr 2
  • Doku doesn't compile on stable Rust

    Doku doesn't compile on stable Rust

    $ cargo +stable --version
    cargo 1.55.0 (32da73ab1 2021-08-23)
    
    $ cargo +stable check
    Compiling proc-macro2 v1.0.29
    Compiling syn v1.0.77
    Compiling unicode-xid v0.2.2
    Compiling version_check v0.9.3
    Compiling fnv v1.0.7
    Compiling strsim v0.10.0
    Compiling ident_case v1.0.1
    Compiling serde_derive v1.0.130
    Compiling serde v1.0.130
    Compiling anyhow v1.0.44
    Compiling proc-macro-error-attr v1.0.4
    Compiling proc-macro-error v1.0.4
    Checking quote v1.0.9
    Checking darling_core v0.13.0
    Checking doku-test v0.0.0 (/home/felix/workspace/doku/doku-test)
    error[E0554]: `#![feature]` may not be used on the stable release channel
    --> /home/felix/.cargo/registry/src/github.com-1ecc6299db9ec823/darling_core-0.13.0/src/lib.rs:2:38
    |
    2 | #![cfg_attr(feature = "diagnostics", feature(proc_macro_diagnostic))]
    |                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    For more information about this error, try `rustc --explain E0554`.
    error: could not compile `darling_core` due to previous error
    warning: build failed, waiting for other jobs to finish...
    error: build failed
    

    Disabling the "diagnostics" feature of darling_core fixes the problem. Could you please make a patch release with that change? :)

    opened by Nutomic 2
  • "the trait bound ... is not satisfied" should point at the offending field

    Compiling this code:

    #[derive(Document)]
    struct Foo {
      bar: Bar,
    }
    
    struct Bar;
    

    ... will throw the trait bound ... is not satisfied at Document - it'd be nice if we pointed out bar: Bar instead, as does e.g. Serde.

    C-enhancement A-macro 
    opened by Patryk27 1
  • Comments in separate line

    Comments in separate line

    I think it would be nice if comments in the output could be on a separate line before the data. That gives more space for comments, and also allows for multi-line comments to be included.

    Maybe there could be a parameter on doku::to_json() to choose between multiple predefined output formats.

    C-enhancement A-printer 
    opened by Nutomic 1
  • Fix minor bug with generics + traits

    Fix minor bug with generics + traits

    The recently implemented generic parser was not working properly when any trait bounds were specified at the trait definition (instead of using a where clause), so like this:

    struct Something<T: Trait> {
      /* ... */
    }
    

    This commit fixes the aforementioned bug.

    opened by thanasis2028 0
  • TOML Support

    TOML Support

    A first working implementation of TOML pretty printer (issue #7). A new module toml has been created (which started as a copy of json so there is some duplicated code). In addition, all the tests have been modified to also produce and assert on toml output, too.

    Some things that are still pending / under consideration:

    • [ ] Try reducing duplicated code by moving it to a common module. One issue with this task is that in some files (e.g. formatting/*.rs) the code is practically the same but the documentation examples are different.
    • [ ] Directly documenting anything other than a struct or an enum (e.g. an array or an optional) is not very useful and produces invalid TOML. Maybe we shouldn't allow this? For our tests, I have wrapped these types in a struct so that valid TOML is produced.
    • [ ] The code uses panic!() at various points to avoid cases that can't be documented properly (and would panic even more if the task above is implemented). Consider modifying to_toml() and all underlying methods to return a Result.
    • [ ] Consider making json, toml features so that the user can choose to not compile any printer that they don't want (although compile time is not very high anyway).
    opened by thanasis2028 0
  • v0.10: Support for values, more documentation, more tunables

    v0.10: Support for values, more documentation, more tunables

    My current branch got a bit bigger than I initially planned, so here's one, larger merge request that:

    • closes https://github.com/anixe/doku/issues/1
    • closes https://github.com/anixe/doku/issues/2
    • closes https://github.com/anixe/doku/issues/8

    Status: most of the code is ready; just have to write a few more tests and polish-up the docs 😃

    opened by Patryk27 0
  • Support for RON

    Support for RON

    The TOML support is awesome! It would be cool if this project also supported RON. I've used it on several projects for configuration. It supports comments like TOML but is also good for nested data like JSON .

    C-enhancement A-printer 
    opened by kellpossible 1
  • Special characters in field names should be escaped

    Special characters in field names should be escaped

    This code:

    use serde::Serialize;
    
    #[derive(Default, Serialize)]
    struct Foo {
        #[serde(rename = "bar\"")]
        bar: String,
    }
    
    fn main() {
        println!("{}", serde_json::to_string_pretty(&Foo::default()).unwrap());
    }
    

    ... returns:

    {
      "bar\"": ""
    }
    

    ... but Doku will document it in a wrong way:

    {
      "bar"": "string"
    }
    
    C-bug A-printer 
    opened by Patryk27 1
  • Generate output based on `[default()]` attribute

    Generate output based on `[default()]` attribute

    We generate doku output using command doku::to_json_fmt_val(&fmt, &Settings::default(). But even so, the default values are completely ignored, and we need to specify #[doku(example = ...)] as well, which usually means writing the same thing twice.

    For example this Rust code:

      /// Address where pictrs is available (for image hosting)
      #[default("http://pictrs:8080")]
      pub url: String,
    

    Generates this output:

        # Address where pictrs is available (for image hosting)
        url: "string"
    

    Additionally, i think it would make sense that specifying #[default(None)] for an Option would have the same effect as #[doku(skip)].

    opened by Nutomic 2
Owner
ANIXE
ANIXE
Minimal, flexible framework for implementing solutions to Advent of Code in Rust

This is advent_of_code_traits, a minimal, flexible framework for implementing solutions to Advent of Code in Rust.

David 8 Apr 17, 2022
Code for connecting an RP2040 to a Bosch BNO055 IMU and having the realtime orientation data be sent to the host machine via serial USB

Code for connecting an RP2040 (via Raspberry Pi Pico) to a Bosch BNO055 IMU (via an Adafruit breakout board) and having the realtime orientation data be sent to the host machine via serial USB.

Gerald Nash 3 Nov 4, 2022
Code examples, data structures, and links from my book, Rust Atomics and Locks.

This repository contains the code examples, data structures, and links from Rust Atomics and Locks. The examples from chapters 1, 2, 3, and 8 can be f

Mara Bos 338 Jan 6, 2023
A formal, politely verbose programming language for building next-gen reliable applications

vfpl Pronounced "Veepl", the f is silent A politely verbose programming language for building next-gen reliable applications Syntax please initialize

VFPL 4 Jun 27, 2022
Async implementation of the StatusNotifierItem and DbusMenu protocols for building system trays.

System Tray An async implementation of the StatusNotifierItem and DbusMenu protocols for building system trays. Requires Tokio. Example use system_tra

Jake Stanger 3 Mar 29, 2024
Framework is a detector for different frameworks in one projects

Framework is a detector for different frameworks in one projects Usage use

Inherd OS Team (硬核开源小组) 3 Oct 24, 2022
A new comfortable back end framework for rustaceans.

Black Tea is a new Rust back end framework based on hyper. We are enthusiastic to provide developers some enhanced features and comfortable coding experience.

Rui Li 10 Dec 13, 2021
A framework for iterating over collections of types implementing a trait without virtual dispatch

zero_v Zero_V is an experiment in defining behavior over collections of objects implementing some trait without dynamic polymorphism.

null 13 Jul 28, 2022
Another Async IO Framework based on io_uring

kbio, the Async IO Framework based on io_uring, is used in KuiBaDB to implement async io. Features Support multi-threading concurrent task submission.

KuiBaDB 59 Oct 31, 2022
Better error messages for axum framework.

axum-debug This is a debugging crate that provides better error messages for axum framework. axum is a great framework for developing web applications

Eray Karatay 3 Feb 3, 2022
Rust implementation of the Edge IoT framework

A Rust-based implementation of Edge-rs for the Raspberry Pi Pico Getting started For more details see the following article on getting started for get

Jim Hodapp 4 Apr 14, 2022
tracing - a framework for instrumenting Rust programs to collect structured, event-based diagnostic information

tracing-appender Writers for logging events and spans Documentation | Chat Overview tracing is a framework for instrumenting Rust programs to collect

Cris Liao 1 Mar 9, 2022
Rate limit middleware for poem web framework

Rate limit middleware for Poem framework Usage Check examples, poem-ratelimit is available on crates.io. A yaml configuration file is used to set limi

Rui Li 5 Sep 22, 2022
Ector is an open source async, no-alloc actor framework for embedded devices

Ector is an open source async, no-alloc actor framework for embedded devices. Ector is an open source async, no-alloc actor framework for embedded dev

Drogue IoT 11 Dec 15, 2022
Functional testing framework for AVR binaries, powered by simavr.

Functional testing framework for AVR binaries, powered by simavr. tl;dr get your microcontroller's firmware black-box-tested in seconds!

Patryk Wychowaniec 14 Nov 16, 2022
Examples of how to use Rust with Serverless Framework, Lambda, API Gateway v1 and v2, SQS, GraphQL, etc

Rust Serverless Examples All examples live in their own directories: project: there is nothing here, just a simple cargo new project_name with a custo

Fernando Daciuk 9 Dec 17, 2022
A rust `tracing` compatible framework inspired by log4rs.

trace4rs This crate allows users to configure output from tracing in the same way as you would configure the output of log4rs. Overview For a usage ex

Imperva 5 Oct 24, 2022
Monorep for fnRPC (high performance serverless rpc framework)

fnrpc Monorep for fnRPC (high performance serverless rpc framework) cli Cli tool help build and manage functions Create RPC functions Create & Manage

Faasly 3 Dec 21, 2022
A framework experience for Yew.

stackable A framework experience for Yew. Stackable provides a development stack with: Tooling around Server-side Rendering Support. An easy-to-use, S

Kaede Hoshikawa 8 Dec 30, 2022