sidoc

Check-in Differences
Login

Check-in Differences

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From sidoc-0.1.0 To sidoc-0.1.1

2024-11-07
09:13
change log. check-in: ef4f5138fd user: jan tags: trunk
08:53
Update crate metadata. Happy pedantic clippy. check-in: c84ce1ad85 user: jan tags: trunk, sidoc-0.1.1
2024-11-06
18:42
Happy pedantic clippy. check-in: c1d63406c5 user: jan tags: trunk
2022-09-29
18:48
Move repo. Clippy. check-in: 98b35525c4 user: jan tags: trunk
2020-09-12
13:56
Ignore gitignore. check-in: 4bf4749324 user: jan tags: trunk, sidoc-0.1.0
13:54
Move from temporary repository. check-in: b274896be6 user: jan tags: trunk

Changes to .efiles.

1



2





3
1
2
3
4

5
6
7
8
9
10

+
+
+
-
+
+
+
+
+

Cargo.toml
README.md
www/index.md
www/changelog.md
src/*.rs
src/err.rs
src/lib.rs
src/builder.rs
src/doc.rs
src/render.rs
tests/*.rs

Changes to .fossil-settings/ignore-glob.

1
2
3
4
1

2
3

-


.*.swp
.gitignore
Cargo.lock
target

Changes to Cargo.toml.

1
2
3

4
5

6


7
8
9
10















11







1
2

3


4
5
6
7
8



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31


-
+
-
-
+

+
+

-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
[package]
name = "sidoc"
version = "0.1.0"
version = "0.1.1"
authors = ["Jan Danielsson <jan.danielsson@qrnch.com>"]
edition = "2018"
edition = "2021"
license = "0BSD"
# https://crates.io/category_slugs
categories = [ "template-engine" ]
keywords = [ "text", "document", "html" ]
repository = "https://github.com/openqrnch/sidoc"
description = "Library for generating structured/scoped indented documents."

repository = "https://repos.qrnch.tech/pub/sidoc"
description = "Generate structured/scoped indented documents."
exclude = [
  ".fossil-settings",
  ".efiles",
  ".fslckout",
  "www",
  "bacon.toml",
  "rustfmt.toml"
]

# https://doc.rust-lang.org/cargo/reference/manifest.html#the-badges-section
[badges]
maintenance = { status = "passively-maintained" }

[dependencies]

[lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }
cargo = { level = "warn", priority = -1 }

Added README.md.





1
2
3
4
+
+
+
+
# sidoc

Template engine designed to generate indented documents.

Added bacon.toml.































































































































1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
# This is a configuration file for the bacon tool
#
# Complete help on configuration: https://dystroy.org/bacon/config/
# 
# You may check the current default at
#   https://github.com/Canop/bacon/blob/main/defaults/default-bacon.toml

default_job = "clippy-all"

[jobs.check]
command = ["cargo", "check", "--color", "always"]
need_stdout = false

[jobs.check-all]
command = ["cargo", "check", "--all-targets", "--color", "always"]
need_stdout = false

# Run clippy on the default target
[jobs.clippy]
command = [
    "cargo", "clippy",
    "--color", "always",
]
need_stdout = false

# Run clippy on all targets
# To disable some lints, you may change the job this way:
#    [jobs.clippy-all]
#    command = [
#        "cargo", "clippy",
#        "--all-targets",
#        "--color", "always",
#    	 "--",
#    	 "-A", "clippy::bool_to_int_with_if",
#    	 "-A", "clippy::collapsible_if",
#    	 "-A", "clippy::derive_partial_eq_without_eq",
#    ]
# need_stdout = false
[jobs.clippy-all]
command = [
    "cargo", "clippy",
    "--all-targets",
    "--color", "always",
]
need_stdout = false

# This job lets you run
# - all tests: bacon test
# - a specific test: bacon test -- config::test_default_files
# - the tests of a package: bacon test -- -- -p config
[jobs.test]
command = [
    "cargo", "test", "--color", "always",
    "--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
]
need_stdout = true

[jobs.nextest]
command = [
    "cargo", "nextest", "run",
    "--color", "always",
    "--hide-progress-bar", "--failure-output", "final"
]
need_stdout = true
analyzer = "nextest"

[jobs.doc]
command = ["cargo", "doc", "--color", "always", "--no-deps"]
need_stdout = false

# If the doc compiles, then it opens in your browser and bacon switches
# to the previous job
[jobs.doc-open]
command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"]
need_stdout = false
on_success = "back" # so that we don't open the browser at each change

# You can run your application and have the result displayed in bacon,
# if it makes sense for this crate.
# Don't forget the `--color always` part or the errors won't be
# properly parsed.
[jobs.run]
command = [
    "cargo", "run",
    "--color", "always",
    # put launch parameters for your program behind a `--` separator
]
need_stdout = true
allow_warnings = true
background = true

# Run your long-running application (eg server) and have the result displayed in bacon.
# For programs that never stop (eg a server), `background` is set to false
# to have the cargo run output immediately displayed instead of waiting for
# program's end.
# 'on_change_strategy' is set to `kill_then_restart` to have your program restart
# on every change (an alternative would be to use the 'F5' key manually in bacon).
# If you often use this job, it makes sense to override the 'r' key by adding
# a binding `r = job:run-long` at the end of this file .
[jobs.run-long]
command = [
    "cargo", "run",
    "--color", "always",
    # put launch parameters for your program behind a `--` separator
]
need_stdout = true
allow_warnings = true
background = false
on_change_strategy = "kill_then_restart"

# This parameterized job runs the example of your choice, as soon
# as the code compiles.
# Call it as
#    bacon ex -- my-example
[jobs.ex]
command = ["cargo", "run", "--color", "always", "--example"]
need_stdout = true
allow_warnings = true

# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal global prefs.toml file instead.
[keybindings]
# alt-m = "job:my-job"
c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target

Changes to src/builder.rs.

1
2
3

4
5
6
7
8
9
10

11
12

13
14
15
16
17
18
19
20
21
22

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39



40
41
42
43
44
45
46
47
48
49
50




51
52
53
54
55
56
57

58
59
60
61
62
63
64
65
66

67
68
69
70
71
72
73
74
75
76

77
78
79
80
81
82
83
84
85



86
87
88
89
90
91
92
93
94
95
96
97
98
1
2
3
4
5
6
7
8
9
10
11
12
13

14



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111



+







+

-
+
-
-
-







+

















+
+
+











+
+
+
+







+









+










+









+
+
+













use crate::{Doc, Error, Node};

/// Constructor for `Doc` objects.
#[derive(Default)]
pub struct Builder {
  nodes: Vec<Node>,
  scope_stack: Vec<Option<String>>
}

impl Builder {
  /// Create a new `Doc` builder context.
  #[must_use]
  pub fn new() -> Self {
    Builder {
    Self::default()
      nodes: Vec::new(),
      scope_stack: Vec::new()
    }
  }

  /// Begin a scope, pushing an optional scope terminator to the internal scope
  /// stack.
  ///
  /// If the scope generated using a terminator line, that line will appended
  /// to the document when the scope is closed using the `exit()` method.
  #[allow(clippy::needless_pass_by_value)]
  pub fn scope<L: ToString, K: ToString>(
    &mut self,
    begin_line: L,
    term_line: Option<K>
  ) -> &mut Self {
    self.nodes.push(Node::BeginScope(begin_line.to_string()));
    if let Some(ln) = term_line {
      self.scope_stack.push(Some(ln.to_string()));
    } else {
      self.scope_stack.push(None);
    }
    self
  }

  /// Leave a previously entered scope.
  ///
  /// If the `scope()` call that created the current scope
  ///
  /// # Panics
  /// The scope stack must not be empty.
  pub fn exit(&mut self) -> &mut Self {
    if let Some(s) = self.scope_stack.pop().unwrap() {
      self.nodes.push(Node::EndScope(Some(s)));
    } else {
      self.nodes.push(Node::EndScope(None));
    }
    self
  }

  /// Leave previously entered scope, adding a line passed by the caller rather
  /// than the scope stack.
  ///
  /// # Panics
  /// The scope stack must not be empty.
  #[allow(clippy::needless_pass_by_value)]
  pub fn exit_line<L: ToString>(&mut self, line: L) -> &mut Self {
    let _ = self.scope_stack.pop().unwrap();
    self.nodes.push(Node::EndScope(Some(line.to_string())));
    self
  }

  /// Add a new line at current scope.
  #[allow(clippy::needless_pass_by_value)]
  pub fn line<L: ToString>(&mut self, line: L) -> &mut Self {
    self.nodes.push(Node::Line(line.to_string()));
    self
  }

  /// Add a named optional reference.
  ///
  /// References are placeholders for other documents.  An optional reference
  /// means that this reference does not need to be resolved by the renderer.
  #[allow(clippy::needless_pass_by_value)]
  pub fn optref<N: ToString>(&mut self, name: N) -> &mut Self {
    self.nodes.push(Node::OptRef(name.to_string()));
    self
  }

  /// Add a named required reference.
  ///
  /// References are placeholders for other documents.  A required reference
  /// must be resolved by the renderer or it will return an error to its
  /// caller.
  #[allow(clippy::needless_pass_by_value)]
  pub fn reqref<N: ToString>(&mut self, name: N) -> &mut Self {
    self.nodes.push(Node::ReqRef(name.to_string()));
    self
  }

  /// Generate a `Doc` object from this document.
  ///
  /// The document must be properly nested before calling this function,
  /// meaning all scopes it opened must be closed.
  ///
  /// # Errors
  /// [`Error::BadNesting`] means one or more scopes are still open.
  pub fn build(self) -> Result<Doc, Error> {
    if self.scope_stack.is_empty() {
      Ok(Doc { nodes: self.nodes })
    } else {
      Err(Error::BadNesting(format!(
        "{} scope(s) remaining",
        self.scope_stack.len()
      )))
    }
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to src/doc.rs.

1
2
3

4
5
6
7
8

9
10

11
12
13
14
1
2
3
4
5
6
7
8
9
10
11

12
13
14
15
16



+





+

-
+




use crate::Node;

/// A "Doc" represents a set of lines and references to other Doc's.
#[derive(Default)]
pub struct Doc {
  pub(crate) nodes: Vec<Node>
}

impl Doc {
  #[must_use]
  pub fn new() -> Self {
    Doc { nodes: Vec::new() }
    Self::default()
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to src/err.rs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15



16
17
18
19
20
1
2
3
4
5
6
7
8
9
10
11
12



13
14
15
16
17
18
19
20












-
-
-
+
+
+





use std::fmt;

#[derive(Debug)]
pub enum Error {
  BadRef(String),
  BadNesting(String)
}

impl std::error::Error for Error {}

impl fmt::Display for Error {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match &*self {
      Error::BadRef(s) => write!(f, "Bad reference error; {}", s),
      Error::BadNesting(s) => write!(f, "Bad nesting error; {}", s)
    match self {
      Self::BadRef(s) => write!(f, "Bad reference error; {s}"),
      Self::BadNesting(s) => write!(f, "Bad nesting error; {s}")
    }
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to src/render.rs.

1

2
3
4
5

6
7
8
9
10
11
12
13

14
15
16


17
18
19
20
21
22










23

24
25
26
27
28



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

48
49
50
51

52
53
54
55
56
57
58

59
60
61
62
63
64
65
66
67

68
69
70
71
72
73
74

75
76
77
78
79
80
81

1

2
3
4
5
6
7
8
9
10
11


12



13
14
15
16
17
18
19

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

58
59
60


61

62
63
64
65
66

67
68
69
70
71
72
73
74
75

76
77
78
79
80
81
82

83
84
85
86
87
88
89
90
-
+
-



+






-
-
+
-
-
-
+
+





-
+
+
+
+
+
+
+
+
+
+

+





+
+
+


















-
+


-
-
+
-





-
+








-
+






-
+







use std::collections::HashMap;
use std::{collections::HashMap, sync::Arc};
use std::sync::Arc;

use crate::{Doc, Error, Node};

#[allow(clippy::module_name_repetitions)]
pub struct RenderContext {
  ichar: char,
  iwidth: usize,
  dict: HashMap<String, Arc<Doc>>
}


impl RenderContext {
impl Default for RenderContext {
  /// Create a new render context object.
  pub fn new() -> Self {
    RenderContext {
  fn default() -> Self {
    Self {
      ichar: ' ',
      iwidth: 2,
      dict: HashMap::new()
    }
  }

}


impl RenderContext {
  /// Create a new render context object.
  #[must_use]
  pub fn new() -> Self {
    Self::default()
  }

  /// Add a shared `Doc` document to the render context.
  #[allow(clippy::needless_pass_by_value)]
  pub fn doc<N: ToString>(&mut self, id: N, doc: Arc<Doc>) {
    self.dict.insert(id.to_string(), Arc::clone(&doc));
  }

  /// Render a root `Doc`, resolving all references.
  ///
  /// # Errors
  /// [`Error::BadRef`] means a referenced document does not exist.
  pub fn render(&self, name: &str) -> Result<String, Error> {
    struct IterNode<'a> {
      lst: &'a Vec<Node>,
      idx: usize
    }
    let mut iterstack = Vec::new();
    let mut out = String::new();
    let mut indent: usize = 0;

    // Generate single indent string
    let istr = self.ichar.to_string().repeat(self.iwidth);

    if let Some(dict) = self.dict.get(name) {
      iterstack.push(IterNode {
        lst: &dict.nodes,
        idx: 0
      });
    } else {
      return Err(Error::BadRef(format!("Missing root document '{}'", name)));
      return Err(Error::BadRef(format!("Missing root document '{name}'")));
    }

    'outer: while !iterstack.is_empty() {
      let mut it = iterstack.pop().unwrap();
    'outer: while let Some(mut it) = iterstack.pop() {

      while it.idx < it.lst.len() {
        match &it.lst[it.idx] {
          Node::BeginScope(s) => {
            let is = istr.repeat(indent);
            out.push_str(&is);
            out.push_str(&s);
            out.push_str(s);
            out.push('\n');
            indent += 1;
          }
          Node::EndScope(s) => {
            indent -= 1;
            if let Some(s) = s {
              let is = istr.repeat(indent);
              out.push_str(&is);
              out.push_str(&s);
              out.push_str(s);
              out.push('\n');
            }
          }
          Node::Line(s) => {
            let is = istr.repeat(indent);
            out.push_str(&is);
            out.push_str(&s);
            out.push_str(s);
            out.push('\n');
          }
          Node::OptRef(name) => {
            if let Some(dict) = self.dict.get(name) {
              iterstack.push(IterNode {
                lst: it.lst,
                idx: it.idx + 1
96
97
98
99
100
101
102
103
104
105



106
107

108
109
110
111
112
113
114
115
116
117
118
119
105
106
107
108
109
110
111



112
113
114


115

116
117
118
119
120
121
122
123
124
125
126







-
-
-
+
+
+
-
-
+
-











              });

              iterstack.push(IterNode {
                lst: &dict.nodes,
                idx: 0
              });
              continue 'outer;
            } else {
              return Err(Error::BadRef(format!(
                "Missing required document '{}'",
            }
            return Err(Error::BadRef(format!(
              "Missing required document '{name}'"
                name
              )));
            )));
            }
          }
        }
        it.idx += 1;
      }
    }

    Ok(out)
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Added www/changelog.md.























1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
# Change Log

⚠️  indicates a breaking change.

## [Unreleased]

[Details](/vdiff?from=sidoc-0.1.0&to=trunk)

### Added

- Derive `Default` on `Doc` and `RenderContext`

### Changed

### Removed

---

## [0.1.0] - 2020-09-12

Initial release

Added www/index.md.


















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
# sidoc

Template engine designed to generate indented documents.


## Change log

The details of changes can always be found in the timeline, but for a
high-level view of changes between released versions there's a manually
maintained [Change Log](./changelog.md).


## Project Status

sidoc is passively maintained.  It will receive updates and fixes and needed,
but is currently considered to be feature complete.