lemmy_help/
lib.rs

1#[cfg(feature = "vimdoc")]
2pub mod vimdoc;
3
4pub mod lexer;
5pub mod parser;
6
7use std::{fmt::Display, str::FromStr};
8
9use chumsky::prelude::Simple;
10
11use parser::{
12    Alias, Brief, Class, Divider, Field, Func, Module, Node, Param, Return, See, Tag, Type, Usage,
13};
14
15use crate::lexer::TagType;
16
17pub trait Visitor {
18    type R;
19    type S;
20    fn module(&self, n: &Module, s: &Self::S) -> Self::R;
21    fn divider(&self, n: &Divider, s: &Self::S) -> Self::R;
22    fn brief(&self, n: &Brief, s: &Self::S) -> Self::R;
23    fn tag(&self, n: &Tag, s: &Self::S) -> Self::R;
24    fn func(&self, n: &Func, s: &Self::S) -> Self::R;
25    fn params(&self, n: &[Param], s: &Self::S) -> Self::R;
26    fn r#returns(&self, n: &[Return], s: &Self::S) -> Self::R;
27    fn class(&self, n: &Class, s: &Self::S) -> Self::R;
28    fn fields(&self, n: &[Field], s: &Self::S) -> Self::R;
29    fn alias(&self, n: &Alias, s: &Self::S) -> Self::R;
30    fn r#type(&self, n: &Type, s: &Self::S) -> Self::R;
31    fn toc(&self, n: &str, nodes: &[Node], s: &Self::S) -> Self::R;
32    fn see(&self, n: &See, s: &Self::S) -> Self::R;
33    fn usage(&self, n: &Usage, s: &Self::S) -> Self::R;
34}
35
36pub trait Accept<T: Visitor> {
37    fn accept(&self, n: &T, s: &T::S) -> T::R;
38}
39
40pub trait Nodes {
41    fn nodes(&self) -> &Vec<Node>;
42}
43
44pub trait FromEmmy: Display {
45    type Settings;
46    fn from_emmy(t: &impl Nodes, s: &Self::Settings) -> Self;
47}
48
49pub trait AsDoc<T: FromEmmy> {
50    fn as_doc(&self, s: &T::Settings) -> T;
51}
52
53#[derive(Debug, Default, PartialEq, Eq)]
54pub enum Layout {
55    #[default]
56    Default,
57    Compact(u8),
58    Mini(u8),
59}
60
61impl FromStr for Layout {
62    type Err = ();
63    fn from_str(s: &str) -> Result<Self, Self::Err> {
64        match s {
65            "default" => Ok(Self::Default),
66            x => {
67                let mut val = x.splitn(2, ':');
68                match (val.next(), val.next()) {
69                    (Some("compact"), n) => Ok(Self::Compact(
70                        n.map_or(0, |x| x.parse().unwrap_or_default()),
71                    )),
72                    (Some("mini"), n) => {
73                        Ok(Self::Mini(n.map_or(0, |x| x.parse().unwrap_or_default())))
74                    }
75                    _ => Err(()),
76                }
77            }
78        }
79    }
80}
81
82#[derive(Debug)]
83pub struct Settings {
84    /// Prefix `function` name with `---@mod` name
85    pub prefix_func: bool,
86    /// Prefix `---@alias` tag with `---@mod/return` name
87    pub prefix_alias: bool,
88    /// Prefix `---@class` tag with `---@mod/return` name
89    pub prefix_class: bool,
90    /// Prefix `---@type` tag with `---@mod` name
91    pub prefix_type: bool,
92    /// Expand `?` to `nil|<type>`
93    pub expand_opt: bool,
94    /// Vimdoc text layout
95    pub layout: Layout,
96    /// Controls the indent width
97    pub indent_width: usize,
98}
99
100impl Default for Settings {
101    fn default() -> Self {
102        Self {
103            prefix_func: false,
104            prefix_alias: false,
105            prefix_class: false,
106            prefix_type: false,
107            expand_opt: false,
108            layout: Layout::default(),
109            indent_width: 4,
110        }
111    }
112}
113
114#[derive(Debug, Default)]
115pub struct LemmyHelp {
116    nodes: Vec<Node>,
117}
118
119impl Nodes for LemmyHelp {
120    fn nodes(&self) -> &Vec<Node> {
121        &self.nodes
122    }
123}
124
125impl<T: FromEmmy> AsDoc<T> for LemmyHelp {
126    fn as_doc(&self, s: &T::Settings) -> T {
127        T::from_emmy(self, s)
128    }
129}
130
131impl LemmyHelp {
132    /// Creates a new parser instance
133    ///
134    /// ```
135    /// use lemmy_help::LemmyHelp;
136    ///
137    /// LemmyHelp::new();
138    /// ```
139    pub fn new() -> Self {
140        Self { nodes: vec![] }
141    }
142
143    /// Parse given lua source code to generate AST representation
144    ///
145    /// ```
146    /// use lemmy_help::{LemmyHelp, Nodes};
147    ///
148    /// let mut lemmy = LemmyHelp::default();
149    /// let src = r#"
150    /// local U = {}
151    ///
152    /// ---Add two integar and print it
153    /// ---@param this number First number
154    /// ---@param that number Second number
155    /// function U.sum(this, that)
156    ///     print(this + that)
157    /// end
158    ///
159    /// return U
160    /// "#;
161    ///
162    /// let ast = lemmy.parse(&src).unwrap();
163    /// assert!(!ast.nodes().is_empty());
164    /// ```
165    pub fn parse(&mut self, src: &str) -> Result<&Self, Vec<Simple<TagType>>> {
166        self.nodes.append(&mut Node::new(src)?);
167
168        Ok(self)
169    }
170
171    /// Similar to [`LemmyHelp::parse`], but specifically used for generating vimdoc
172    pub fn for_help(
173        &mut self,
174        src: &str,
175        settings: &Settings,
176    ) -> Result<&Self, Vec<Simple<TagType>>> {
177        let mut nodes = Node::new(src)?;
178
179        if let Some(Node::Export(export)) = nodes.pop() {
180            let module = match nodes.iter().rev().find(|x| matches!(x, Node::Module(_))) {
181                Some(Node::Module(m)) => m.name.to_owned(),
182                _ => export.to_owned(),
183            };
184
185            for ele in nodes {
186                match ele {
187                    Node::Export(..) => {}
188                    Node::Func(mut func) => {
189                        if func.prefix.left.as_deref() == Some(&export) {
190                            if settings.prefix_func {
191                                func.prefix.right = Some(module.to_owned());
192                            }
193                            self.nodes.push(Node::Func(func));
194                        }
195                    }
196                    Node::Type(mut typ) => {
197                        if typ.prefix.left.as_deref() == Some(&export) {
198                            if settings.prefix_type {
199                                typ.prefix.right = Some(module.to_owned());
200                            }
201                            self.nodes.push(Node::Type(typ));
202                        }
203                    }
204                    Node::Alias(mut alias) => {
205                        if settings.prefix_alias {
206                            alias.prefix.right = Some(module.to_owned());
207                        }
208                        self.nodes.push(Node::Alias(alias))
209                    }
210                    Node::Class(mut class) => {
211                        if settings.prefix_class {
212                            class.prefix.right = Some(module.to_owned());
213                        }
214                        self.nodes.push(Node::Class(class))
215                    }
216                    _ => self.nodes.push(ele),
217                }
218            }
219        };
220
221        Ok(self)
222    }
223}