Skip to main content

links_notation/
lib.rs

1pub mod comments;
2pub mod format_config;
3pub mod parser;
4pub mod parser_config;
5
6use comments::strip_comments;
7use format_config::FormatConfig;
8pub use parser_config::ParserConfig;
9use std::borrow::Cow;
10
11// Re-export the lino! macro when the macro feature is enabled
12#[cfg(feature = "macro")]
13pub use links_notation_macro::lino;
14use std::error::Error as StdError;
15use std::fmt;
16
17/// The version of this crate, taken from `Cargo.toml` at compile time.
18///
19/// A tool that reports which parser produced a result should read it from here
20/// rather than from its own package, which is how the benchmark report came to
21/// claim the version of the benchmark instead of the version of the parser.
22///
23/// # Examples
24/// ```
25/// assert!(!links_notation::VERSION.is_empty());
26/// ```
27pub const VERSION: &str = env!("CARGO_PKG_VERSION");
28
29/// Error type for Lino parsing
30#[derive(Debug)]
31pub enum ParseError {
32    /// Input string is empty or contains only whitespace
33    EmptyInput,
34    /// The document does not parse, and this is where it stopped
35    SyntaxError(SyntaxError),
36    /// Internal parser error
37    InternalError(String),
38}
39
40impl fmt::Display for ParseError {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self {
43            ParseError::EmptyInput => write!(f, "Empty input"),
44            ParseError::SyntaxError(error) => write!(f, "Syntax error at {}", error),
45            ParseError::InternalError(msg) => write!(f, "Internal error: {}", msg),
46        }
47    }
48}
49
50impl StdError for ParseError {}
51
52/// The number of characters of the offending line an error message quotes.
53///
54/// A message has to fit in a log line, and the whole point of quoting one line
55/// of context is that the message does not grow with the size of the document.
56const QUOTED_LINE_WIDTH: usize = 80;
57
58/// What a message writes in place of the part of a long line it left out.
59const ELLIPSIS: &str = "...";
60
61/// A syntax error, with the position in the document it was found at.
62///
63/// The position is the furthest one the parser reached, which is the character
64/// the document stops making sense at rather than the point the last
65/// alternative gave up on.
66///
67/// # Examples
68/// ```
69/// use links_notation::{parse_lino, ParseError};
70///
71/// let error = parse_lino("ci_gate x\nstage: rust: nextest\n").unwrap_err();
72/// let ParseError::SyntaxError(error) = error else { panic!("expected a syntax error") };
73/// assert_eq!((error.line, error.column), (2, 12));
74/// assert_eq!(error.found, Some(':'));
75/// ```
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct SyntaxError {
78    /// Byte offset of the offending position from the start of the document.
79    pub offset: usize,
80    /// Line the offending position is on, counted from 1.
81    pub line: usize,
82    /// Column the offending position is at, in characters, counted from 1.
83    pub column: usize,
84    /// What could have continued the document at this position. Empty when the
85    /// parser stopped somewhere it names no expectation for.
86    pub expected: Vec<String>,
87    /// The character found instead, or `None` at the end of the document.
88    pub found: Option<char>,
89    /// The offending line, as written, without its line ending.
90    pub line_text: String,
91}
92
93impl SyntaxError {
94    /// The one-line summary: where the parser stopped, what could have stood
95    /// there and what does.
96    ///
97    /// # Examples
98    /// ```
99    /// use links_notation::{parse_lino, ParseError};
100    ///
101    /// let ParseError::SyntaxError(error) = parse_lino("a: b: c").unwrap_err() else {
102    ///     panic!("expected a syntax error")
103    /// };
104    /// assert_eq!(
105    ///     error.summary(),
106    ///     r#"line 1, column 5: expected "(", a reference or end of line, found ":""#
107    /// );
108    /// ```
109    pub fn summary(&self) -> String {
110        let found = match self.found {
111            Some(character) => format!("\"{}\"", character.escape_debug()),
112            None => "end of input".to_string(),
113        };
114        match join_alternatives(&self.expected) {
115            Some(expected) => format!(
116                "line {}, column {}: expected {}, found {}",
117                self.line, self.column, expected, found
118            ),
119            None => format!(
120                "line {}, column {}: unexpected {}",
121                self.line, self.column, found
122            ),
123        }
124    }
125
126    /// The offending line with a caret under the offending column, quoted the
127    /// way `rustc` quotes source.
128    ///
129    /// A long line is shown as a window around the caret, so the message stays
130    /// the same size whether the document has ten lines or fifteen hundred.
131    ///
132    /// # Examples
133    /// ```
134    /// use links_notation::{parse_lino, ParseError};
135    ///
136    /// let ParseError::SyntaxError(error) = parse_lino("a: b: c").unwrap_err() else {
137    ///     panic!("expected a syntax error")
138    /// };
139    /// assert_eq!(error.snippet(), "1 | a: b: c\n  |     ^");
140    /// ```
141    pub fn snippet(&self) -> String {
142        let (quoted, column) = quote_line(&self.line_text, self.column);
143        let number = self.line.to_string();
144        let gutter = " ".repeat(number.len());
145        format!(
146            "{} | {}\n{} | {}^",
147            number,
148            quoted,
149            gutter,
150            " ".repeat(column - 1)
151        )
152    }
153}
154
155impl fmt::Display for SyntaxError {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        write!(f, "{}\n{}", self.summary(), self.snippet())
158    }
159}
160
161impl StdError for SyntaxError {}
162
163/// Writes alternatives the way prose does: `a`, `a or b`, `a, b or c`.
164fn join_alternatives(alternatives: &[String]) -> Option<String> {
165    match alternatives {
166        [] => None,
167        [only] => Some(only.clone()),
168        [rest @ .., last] => Some(format!("{} or {}", rest.join(", "), last)),
169    }
170}
171
172/// Cuts `line` down to a window around `column`, and says which column the
173/// offending character sits at in that window. Both columns count from 1.
174fn quote_line(line: &str, column: usize) -> (String, usize) {
175    let characters: Vec<char> = line.chars().collect();
176    if characters.len() <= QUOTED_LINE_WIDTH {
177        return (line.to_string(), column);
178    }
179
180    let target = column - 1;
181    let last_start = characters.len() - QUOTED_LINE_WIDTH;
182    let start = target.saturating_sub(QUOTED_LINE_WIDTH / 2).min(last_start);
183    let end = start + QUOTED_LINE_WIDTH;
184
185    let mut quoted = String::new();
186    if start > 0 {
187        quoted.push_str(ELLIPSIS);
188    }
189    quoted.extend(&characters[start..end]);
190    if end < characters.len() {
191        quoted.push_str(ELLIPSIS);
192    }
193
194    let shift = if start > 0 {
195        ELLIPSIS.chars().count()
196    } else {
197        0
198    };
199    (quoted, target - start + shift + 1)
200}
201
202/// Turns the position the parser stopped at into a line, a column and the line
203/// itself, so the message can point at the defect instead of quoting the rest
204/// of the document.
205fn locate(document: &str, failure: parser::ParseFailure) -> SyntaxError {
206    let offset = failure.offset.min(document.len());
207    let before = &document[..offset];
208    let line = before.matches('\n').count() + 1;
209    let line_start = before.rfind('\n').map_or(0, |position| position + 1);
210    let column = document[line_start..offset].chars().count() + 1;
211    let line_end = document[line_start..]
212        .find('\n')
213        .map_or(document.len(), |position| line_start + position);
214    let line_text = document[line_start..line_end].trim_end_matches('\r');
215
216    SyntaxError {
217        offset,
218        line,
219        column,
220        expected: failure.expected.iter().map(|s| s.to_string()).collect(),
221        found: document[offset..].chars().next(),
222        line_text: line_text.to_string(),
223    }
224}
225
226#[derive(Debug, Clone, PartialEq)]
227pub enum LiNo<T> {
228    Link { id: Option<T>, values: Vec<Self> },
229    Ref(T),
230}
231
232impl<T> LiNo<T> {
233    pub fn is_ref(&self) -> bool {
234        matches!(self, LiNo::Ref(_))
235    }
236
237    pub fn is_link(&self) -> bool {
238        matches!(self, LiNo::Link { .. })
239    }
240
241    /// Creates a new link with the given ID and values.
242    ///
243    /// This method allows creating links with any number of values,
244    /// providing an alternative to tuple conversion for cases where
245    /// more than 12 values are needed.
246    ///
247    /// # Examples
248    /// ```
249    /// use links_notation::LiNo;
250    ///
251    /// // Create a link with many values
252    /// let values: Vec<LiNo<String>> = (1..=20)
253    ///     .map(|i| LiNo::Ref(format!("v{}", i)))
254    ///     .collect();
255    /// let link = LiNo::new(Some("id".to_string()), values);
256    /// ```
257    pub fn new(id: Option<T>, values: Vec<Self>) -> Self {
258        LiNo::Link { id, values }
259    }
260
261    /// Creates a new anonymous link (no ID) with the given values.
262    ///
263    /// # Examples
264    /// ```
265    /// use links_notation::LiNo;
266    ///
267    /// let values = vec![LiNo::Ref("a".to_string()), LiNo::Ref("b".to_string())];
268    /// let link = LiNo::anonymous(values);
269    /// assert_eq!(format!("{}", link), "(a b)");
270    /// ```
271    pub fn anonymous(values: Vec<Self>) -> Self {
272        LiNo::Link { id: None, values }
273    }
274
275    /// Creates a new reference.
276    ///
277    /// # Examples
278    /// ```
279    /// use links_notation::LiNo;
280    ///
281    /// let r: LiNo<String> = LiNo::reference("hello".to_string());
282    /// assert_eq!(format!("{}", r), "hello");
283    /// ```
284    pub fn reference(value: T) -> Self {
285        LiNo::Ref(value)
286    }
287}
288
289/// Builder for creating LiNo links with arbitrary number of values.
290///
291/// This builder provides a fluent API for constructing links when the tuple
292/// conversion (limited to 12 elements) is insufficient.
293///
294/// # Examples
295/// ```
296/// use links_notation::{LiNo, LiNoBuilder};
297///
298/// // Build a link with many string values
299/// let link: LiNo<String> = LiNoBuilder::new()
300///     .id("myLink")
301///     .value("v1")
302///     .value("v2")
303///     .value("v3")
304///     .build();
305/// assert_eq!(format!("{}", link), "(myLink: v1 v2 v3)");
306///
307/// // Build a link with LiNo values
308/// let nested: LiNo<String> = ("inner", "a", "b").into();
309/// let link: LiNo<String> = LiNoBuilder::new()
310///     .id("outer")
311///     .lino(nested)
312///     .value("c")
313///     .build();
314/// assert_eq!(format!("{}", link), "(outer: (inner: a b) c)");
315///
316/// // Build anonymous link
317/// let link: LiNo<String> = LiNoBuilder::new()
318///     .value("a")
319///     .value("b")
320///     .build();
321/// assert_eq!(format!("{}", link), "(a b)");
322/// ```
323#[derive(Debug, Clone, Default)]
324pub struct LiNoBuilder {
325    id: Option<String>,
326    values: Vec<LiNo<String>>,
327}
328
329impl LiNoBuilder {
330    /// Creates a new empty LiNoBuilder.
331    pub fn new() -> Self {
332        Self::default()
333    }
334
335    /// Sets the ID of the link.
336    ///
337    /// If called multiple times, the last value wins.
338    pub fn id(mut self, id: &str) -> Self {
339        self.id = Some(id.to_string());
340        self
341    }
342
343    /// Adds a string value to the link (converted to a Ref).
344    pub fn value(mut self, value: &str) -> Self {
345        self.values.push(LiNo::Ref(value.to_string()));
346        self
347    }
348
349    /// Adds a LiNo value to the link.
350    pub fn lino(mut self, value: LiNo<String>) -> Self {
351        self.values.push(value);
352        self
353    }
354
355    /// Adds multiple string values to the link.
356    pub fn values<I, S>(mut self, values: I) -> Self
357    where
358        I: IntoIterator<Item = S>,
359        S: AsRef<str>,
360    {
361        for v in values {
362            self.values.push(LiNo::Ref(v.as_ref().to_string()));
363        }
364        self
365    }
366
367    /// Adds multiple LiNo values to the link.
368    pub fn linos<I>(mut self, values: I) -> Self
369    where
370        I: IntoIterator<Item = LiNo<String>>,
371    {
372        self.values.extend(values);
373        self
374    }
375
376    /// Builds the final LiNo link.
377    pub fn build(self) -> LiNo<String> {
378        LiNo::Link {
379            id: self.id,
380            values: self.values,
381        }
382    }
383}
384
385/// Type alias for backward compatibility (deprecated).
386#[deprecated(since = "0.3.0", note = "Use LiNoBuilder instead")]
387pub type LinkBuilder = LiNoBuilder;
388
389impl<T: ToString + Clone> LiNo<T> {
390    /// Format the link using FormatConfig configuration.
391    ///
392    /// # Arguments
393    /// * `config` - The FormatConfig to use for formatting
394    ///
395    /// # Returns
396    /// Formatted string representation
397    pub fn format_with_config(&self, config: &FormatConfig) -> String {
398        match self {
399            LiNo::Ref(value) => {
400                let escaped = escape_reference(&value.to_string());
401                if config.less_parentheses {
402                    escaped
403                } else {
404                    format!("({})", escaped)
405                }
406            }
407            LiNo::Link { id, values } => {
408                // Empty link
409                if id.is_none() && values.is_empty() {
410                    return if config.less_parentheses {
411                        String::new()
412                    } else {
413                        "()".to_string()
414                    };
415                }
416
417                // Link with only ID, no values
418                if values.is_empty() {
419                    if let Some(ref id_val) = id {
420                        let escaped_id = escape_reference(&id_val.to_string());
421                        return if config.less_parentheses && !needs_parentheses(&id_val.to_string())
422                        {
423                            escaped_id
424                        } else {
425                            format!("({})", escaped_id)
426                        };
427                    }
428                    return if config.less_parentheses {
429                        String::new()
430                    } else {
431                        "()".to_string()
432                    };
433                }
434
435                // Check if we should use indented format
436                let mut should_indent = false;
437                if config.should_indent_by_ref_count(values.len()) {
438                    should_indent = true;
439                } else {
440                    // Try inline format first to check line length
441                    let values_str = values
442                        .iter()
443                        .map(|v| format_value(v))
444                        .collect::<Vec<_>>()
445                        .join(" ");
446
447                    let test_line = if let Some(ref id_val) = id {
448                        let id_str = escape_reference(&id_val.to_string());
449                        if config.less_parentheses {
450                            format!("{}: {}", id_str, values_str)
451                        } else {
452                            format!("({}: {})", id_str, values_str)
453                        }
454                    } else if config.less_parentheses {
455                        values_str.clone()
456                    } else {
457                        format!("({})", values_str)
458                    };
459
460                    if config.should_indent_by_length(&test_line) {
461                        should_indent = true;
462                    }
463                }
464
465                // Format with indentation if needed
466                if should_indent && !config.prefer_inline {
467                    return self.format_indented(config);
468                }
469
470                // Standard inline formatting
471                let values_str = values
472                    .iter()
473                    .map(|v| format_value(v))
474                    .collect::<Vec<_>>()
475                    .join(" ");
476
477                // Link with values only (null id)
478                if id.is_none() {
479                    if config.less_parentheses {
480                        // Check if all values are simple (no nested values)
481                        let all_simple = values.iter().all(|v| matches!(v, LiNo::Ref(_)));
482                        if all_simple {
483                            return values
484                                .iter()
485                                .map(|v| match v {
486                                    LiNo::Ref(r) => escape_reference(&r.to_string()),
487                                    _ => format_value(v),
488                                })
489                                .collect::<Vec<_>>()
490                                .join(" ");
491                        }
492                        return values_str;
493                    }
494                    return format!("({})", values_str);
495                }
496
497                // Link with ID and values
498                let id_str = escape_reference(&id.as_ref().unwrap().to_string());
499                let with_colon = format!("{}: {}", id_str, values_str);
500                if config.less_parentheses && !needs_parentheses(&id.as_ref().unwrap().to_string())
501                {
502                    with_colon
503                } else {
504                    format!("({})", with_colon)
505                }
506            }
507        }
508    }
509
510    /// Format the link with indentation.
511    fn format_indented(&self, config: &FormatConfig) -> String {
512        match self {
513            LiNo::Ref(value) => {
514                let escaped = escape_reference(&value.to_string());
515                format!("({})", escaped)
516            }
517            LiNo::Link { id, values } => {
518                if id.is_none() {
519                    // Values only - format each on separate line
520                    values
521                        .iter()
522                        .map(|v| format!("{}{}", config.indent_string, format_value(v)))
523                        .collect::<Vec<_>>()
524                        .join("\n")
525                } else {
526                    // Link with ID - format as id:\n  value1\n  value2
527                    let id_str = escape_reference(&id.as_ref().unwrap().to_string());
528                    let mut lines = vec![format!("{}:", id_str)];
529                    for v in values {
530                        lines.push(format!("{}{}", config.indent_string, format_value(v)));
531                    }
532                    lines.join("\n")
533                }
534            }
535        }
536    }
537}
538
539impl<T: ToString> fmt::Display for LiNo<T> {
540    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541        match self {
542            // The empty reference is written as a bare delimiter pair; writing it
543            // as nothing would drop it from the document.
544            LiNo::Ref(value) => {
545                let value = value.to_string();
546                if value.is_empty() {
547                    write!(f, "\"\"")
548                } else {
549                    write!(f, "{}", value)
550                }
551            }
552            LiNo::Link { id, values } => {
553                let id_str = id
554                    .as_ref()
555                    .map(|id| {
556                        let id = id.to_string();
557                        if id.is_empty() {
558                            "\"\": ".to_string()
559                        } else {
560                            format!("{}: ", id)
561                        }
562                    })
563                    .unwrap_or_default();
564
565                if f.alternate() {
566                    // Format top-level as lines
567                    let lines = values
568                        .iter()
569                        .map(|value| {
570                            // For alternate formatting, ensure standalone references are wrapped in parentheses
571                            // so that flattened structures like indented blocks render as "(ref)" lines
572                            match value {
573                                LiNo::Ref(_) => format!("{}({})", id_str, value),
574                                _ => format!("{}{}", id_str, value),
575                            }
576                        })
577                        .collect::<Vec<_>>()
578                        .join("\n");
579                    write!(f, "{}", lines)
580                } else {
581                    let values_str = values
582                        .iter()
583                        .map(|value| value.to_string())
584                        .collect::<Vec<_>>()
585                        .join(" ");
586                    write!(f, "({}{})", id_str, values_str)
587                }
588            }
589        }
590    }
591}
592
593// Convert from parser::Link to LiNo (without flattening)
594impl From<parser::Link> for LiNo<String> {
595    fn from(link: parser::Link) -> Self {
596        if let Some(body) = &link.nested {
597            return transform_nested(body);
598        }
599        if link.values.is_empty() && link.children.is_empty() {
600            if let Some(id) = link.id {
601                LiNo::Ref(id)
602            } else {
603                LiNo::Link {
604                    id: None,
605                    values: vec![],
606                }
607            }
608        } else {
609            let values: Vec<LiNo<String>> = link.values.into_iter().map(|v| v.into()).collect();
610            LiNo::Link {
611                id: link.id,
612                values,
613            }
614        }
615    }
616}
617
618// A parenthesized group is a nested document: its body follows the same rules as
619// the root, so it is flattened the same way. A body that produces a single link
620// collapses to that link, unless the body is a single parenthesized group, which
621// keeps `((a b))` different from `(a b)`.
622fn transform_nested(body: &[parser::Link]) -> LiNo<String> {
623    let links = flatten_links(body.to_vec());
624    let wraps_single_group =
625        body.len() == 1 && body[0].nested.is_some() && body[0].children.is_empty();
626    if links.len() == 1 && !wraps_single_group {
627        return links.into_iter().next().unwrap();
628    }
629    LiNo::Link {
630        id: None,
631        values: links,
632    }
633}
634
635// Helper function to flatten indented structures according to Lino spec
636fn flatten_links(links: Vec<parser::Link>) -> Vec<LiNo<String>> {
637    let mut result = vec![];
638
639    for link in links {
640        flatten_link_recursive(&link, None, &mut result);
641    }
642
643    result
644}
645
646fn flatten_link_recursive(
647    link: &parser::Link,
648    parent: Option<&LiNo<String>>,
649    result: &mut Vec<LiNo<String>>,
650) {
651    // Special case: If this is an indented ID (with colon) with children,
652    // the children should become the values of the link (indented ID syntax)
653    if link.is_indented_id
654        && link.id.is_some()
655        && link.values.is_empty()
656        && !link.children.is_empty()
657    {
658        let child_values: Vec<LiNo<String>> = link
659            .children
660            .iter()
661            .map(|child| {
662                // For indented children, if they have single values, extract them
663                if child.values.len() == 1
664                    && child.values[0].values.is_empty()
665                    && child.values[0].children.is_empty()
666                {
667                    // Use if let to safely extract the ID instead of unwrap()
668                    if let Some(ref id) = child.values[0].id {
669                        LiNo::Ref(id.clone())
670                    } else {
671                        // If no ID, create an empty link
672                        parser::Link {
673                            id: child.id.clone(),
674                            values: child.values.clone(),
675                            children: vec![],
676                            is_indented_id: false,
677                            nested: child.nested.clone(),
678                        }
679                        .into()
680                    }
681                } else {
682                    parser::Link {
683                        id: child.id.clone(),
684                        values: child.values.clone(),
685                        children: vec![],
686                        is_indented_id: false,
687                        nested: child.nested.clone(),
688                    }
689                    .into()
690                }
691            })
692            .collect();
693
694        let current = LiNo::Link {
695            id: link.id.clone(),
696            values: child_values,
697        };
698
699        let combined = if let Some(parent) = parent {
700            // Wrap parent in parentheses if it's a reference
701            let wrapped_parent = match parent {
702                LiNo::Ref(ref_id) => LiNo::Link {
703                    id: None,
704                    values: vec![LiNo::Ref(ref_id.clone())],
705                },
706                link => link.clone(),
707            };
708
709            LiNo::Link {
710                id: None,
711                values: vec![wrapped_parent, current],
712            }
713        } else {
714            current
715        };
716
717        result.push(combined);
718        return; // Don't process children again
719    }
720
721    // Create the current link without children
722    let current = if let Some(body) = &link.nested {
723        transform_nested(body)
724    } else if link.values.is_empty() {
725        if let Some(id) = &link.id {
726            LiNo::Ref(id.clone())
727        } else {
728            LiNo::Link {
729                id: None,
730                values: vec![],
731            }
732        }
733    } else {
734        let values: Vec<LiNo<String>> = link
735            .values
736            .iter()
737            .map(|v| {
738                parser::Link {
739                    id: v.id.clone(),
740                    values: v.values.clone(),
741                    children: vec![],
742                    is_indented_id: false,
743                    nested: v.nested.clone(),
744                }
745                .into()
746            })
747            .collect();
748        LiNo::Link {
749            id: link.id.clone(),
750            values,
751        }
752    };
753
754    // Create the combined link (parent + current) with proper wrapping
755    let combined = if let Some(parent) = parent {
756        // Wrap parent in parentheses if it's a reference
757        let wrapped_parent = match parent {
758            LiNo::Ref(ref_id) => LiNo::Link {
759                id: None,
760                values: vec![LiNo::Ref(ref_id.clone())],
761            },
762            link => link.clone(),
763        };
764
765        // Wrap current in parentheses if it's a reference
766        let wrapped_current = match &current {
767            LiNo::Ref(ref_id) => LiNo::Link {
768                id: None,
769                values: vec![LiNo::Ref(ref_id.clone())],
770            },
771            link => link.clone(),
772        };
773
774        LiNo::Link {
775            id: None,
776            values: vec![wrapped_parent, wrapped_current],
777        }
778    } else {
779        current.clone()
780    };
781
782    result.push(combined.clone());
783
784    // Process children
785    for child in &link.children {
786        flatten_link_recursive(child, Some(&combined), result);
787    }
788}
789
790/// The document as the parser reads it: with the comments blanked out when the
791/// configuration asks for comments, and untouched when it does not.
792///
793/// Blanking keeps every byte of the document where it was, so a position the
794/// parser reports is a position in the document the caller passed in.
795fn prepare<'a>(document: &'a str, config: &ParserConfig) -> Cow<'a, str> {
796    if config.comments {
797        Cow::Owned(strip_comments(document))
798    } else {
799        Cow::Borrowed(document)
800    }
801}
802
803/// Reads a document, with comments.
804///
805/// A `#` written where a line or a token starts opens a comment that runs to
806/// the end of the line; [`parse_lino_with_config`] reads a document without
807/// them.
808///
809/// # Examples
810/// ```
811/// use links_notation::parse_lino;
812///
813/// let parsed = parse_lino("# what the gate checks\nci_gate: rust\n").unwrap();
814/// assert_eq!(format!("{}", parsed), "((ci_gate: rust))");
815/// ```
816pub fn parse_lino(document: &str) -> Result<LiNo<String>, ParseError> {
817    parse_lino_with_config(document, &ParserConfig::default())
818}
819
820/// Reads a document the way `config` says to.
821///
822/// # Examples
823/// ```
824/// use links_notation::{parse_lino_with_config, ParserConfig};
825///
826/// let document = "# a: b";
827/// assert_eq!(
828///     format!("{}", parse_lino_with_config(document, &ParserConfig::new()).unwrap()),
829///     "()"
830/// );
831/// assert!(parse_lino_with_config(document, &ParserConfig::without_comments()).is_err());
832/// ```
833pub fn parse_lino_with_config(
834    document: &str,
835    config: &ParserConfig,
836) -> Result<LiNo<String>, ParseError> {
837    // Handle empty or whitespace-only input by returning empty result
838    if document.trim().is_empty() {
839        return Ok(LiNo::Link {
840            id: None,
841            values: vec![],
842        });
843    }
844
845    let prepared = prepare(document, config);
846    match parser::parse_document_with_diagnostics(&prepared) {
847        Ok(links) => {
848            if links.is_empty() {
849                Ok(LiNo::Link {
850                    id: None,
851                    values: vec![],
852                })
853            } else {
854                // Flatten the indented structure according to Lino spec
855                let flattened = flatten_links(links);
856                Ok(LiNo::Link {
857                    id: None,
858                    values: flattened,
859                })
860            }
861        }
862        Err(failure) => Err(ParseError::SyntaxError(locate(document, failure))),
863    }
864}
865
866// New function that matches C# and JS API - returns collection of links
867pub fn parse_lino_to_links(document: &str) -> Result<Vec<LiNo<String>>, ParseError> {
868    parse_lino_to_links_with_config(document, &ParserConfig::default())
869}
870
871/// Reads a document into a collection of links the way `config` says to.
872///
873/// # Examples
874/// ```
875/// use links_notation::{parse_lino_to_links_with_config, ParserConfig};
876///
877/// let links = parse_lino_to_links_with_config("a: b # why", &ParserConfig::new()).unwrap();
878/// assert_eq!(links.len(), 1);
879/// assert_eq!(format!("{}", links[0]), "(a: b)");
880/// ```
881pub fn parse_lino_to_links_with_config(
882    document: &str,
883    config: &ParserConfig,
884) -> Result<Vec<LiNo<String>>, ParseError> {
885    // Handle empty or whitespace-only input by returning empty collection
886    if document.trim().is_empty() {
887        return Ok(vec![]);
888    }
889
890    let prepared = prepare(document, config);
891    match parser::parse_document_with_diagnostics(&prepared) {
892        Ok(links) => {
893            if links.is_empty() {
894                Ok(vec![])
895            } else {
896                // Flatten the indented structure according to Lino spec
897                let flattened = flatten_links(links);
898                Ok(flattened)
899            }
900        }
901        Err(failure) => Err(ParseError::SyntaxError(locate(document, failure))),
902    }
903}
904
905/// Formats a collection of LiNo links as a multi-line string.
906/// Each link is formatted on a separate line.
907pub fn format_links(links: &[LiNo<String>]) -> String {
908    links
909        .iter()
910        .map(|link| format!("{}", link))
911        .collect::<Vec<_>>()
912        .join("\n")
913}
914
915/// Formats a collection of LiNo links as a multi-line string using FormatConfig.
916/// Supports all formatting options including consecutive link grouping.
917///
918/// # Arguments
919/// * `links` - The collection of links to format
920/// * `config` - The FormatConfig to use for formatting
921///
922/// # Returns
923/// Formatted string in Lino notation
924pub fn format_links_with_config(links: &[LiNo<String>], config: &FormatConfig) -> String {
925    if links.is_empty() {
926        return String::new();
927    }
928
929    // Apply consecutive link grouping if enabled
930    let links_to_format = if config.group_consecutive {
931        group_consecutive_links(links)
932    } else {
933        links.to_vec()
934    };
935
936    links_to_format
937        .iter()
938        .map(|link| link.format_with_config(config))
939        .collect::<Vec<_>>()
940        .join("\n")
941}
942
943/// Groups consecutive links with the same ID.
944///
945/// For example:
946/// ```text
947/// SetA a
948/// SetA b
949/// SetA c
950/// ```
951/// Becomes:
952/// ```text
953/// SetA
954///   a
955///   b
956///   c
957/// ```
958fn group_consecutive_links(links: &[LiNo<String>]) -> Vec<LiNo<String>> {
959    if links.is_empty() {
960        return vec![];
961    }
962
963    let mut grouped = vec![];
964    let mut i = 0;
965
966    while i < links.len() {
967        let current = &links[i];
968
969        // Look ahead for consecutive links with same ID
970        if let LiNo::Link {
971            id: Some(ref current_id),
972            values: ref current_values,
973        } = current
974        {
975            if !current_values.is_empty() {
976                // Collect all values with same ID
977                let mut same_id_values = current_values.clone();
978                let mut j = i + 1;
979
980                while j < links.len() {
981                    if let LiNo::Link {
982                        id: Some(ref next_id),
983                        values: ref next_values,
984                    } = &links[j]
985                    {
986                        if next_id == current_id && !next_values.is_empty() {
987                            same_id_values.extend(next_values.clone());
988                            j += 1;
989                        } else {
990                            break;
991                        }
992                    } else {
993                        break;
994                    }
995                }
996
997                // If we found consecutive links, create grouped link
998                if j > i + 1 {
999                    grouped.push(LiNo::Link {
1000                        id: Some(current_id.clone()),
1001                        values: same_id_values,
1002                    });
1003                    i = j;
1004                    continue;
1005                }
1006            }
1007        }
1008
1009        grouped.push(current.clone());
1010        i += 1;
1011    }
1012
1013    grouped
1014}
1015
1016/// Escape a reference string by adding quotes if necessary.
1017fn escape_reference(reference: &str) -> String {
1018    // The empty reference is written as a bare delimiter pair, so that it reads
1019    // back as itself instead of disappearing from the document.
1020    if reference.is_empty() {
1021        return "\"\"".to_string();
1022    }
1023
1024    let has_single_quote = reference.contains('\'');
1025    let has_double_quote = reference.contains('"');
1026
1027    // A reference that begins with a `#` has to be quoted, or it would read back
1028    // as a comment. A `#` anywhere else in a reference is content
1029    // (`issue#1047`), so only the first character matters.
1030    let needs_quoting = reference.starts_with('#')
1031        || reference.contains(':')
1032        || reference.contains('(')
1033        || reference.contains(')')
1034        || reference.contains(' ')
1035        || reference.contains('\t')
1036        || reference.contains('\n')
1037        || reference.contains('\r')
1038        || has_double_quote
1039        || has_single_quote;
1040
1041    // Handle edge case: reference contains both single and double quotes
1042    if has_single_quote && has_double_quote {
1043        // Escape single quotes and wrap in single quotes
1044        return format!("'{}'", reference.replace('\'', "\\'"));
1045    }
1046
1047    // Prefer single quotes if double quotes are present
1048    if has_double_quote {
1049        return format!("'{}'", reference);
1050    }
1051
1052    // Use double quotes if single quotes are present
1053    if has_single_quote {
1054        return format!("\"{}\"", reference);
1055    }
1056
1057    // Use single quotes for special characters
1058    if needs_quoting {
1059        return format!("'{}'", reference);
1060    }
1061
1062    // No quoting needed
1063    reference.to_string()
1064}
1065
1066/// Check if a string needs to be wrapped in parentheses.
1067fn needs_parentheses(s: &str) -> bool {
1068    s.contains(' ') || s.contains(':') || s.contains('(') || s.contains(')')
1069}
1070
1071/// Format a value within a link.
1072fn format_value<T: ToString>(value: &LiNo<T>) -> String {
1073    match value {
1074        LiNo::Ref(r) => escape_reference(&r.to_string()),
1075        LiNo::Link { id, values } => {
1076            // Simple link with just an ID - don't wrap in extra parentheses
1077            if values.is_empty() {
1078                if let Some(ref id_val) = id {
1079                    return escape_reference(&id_val.to_string());
1080                }
1081                return String::new();
1082            }
1083            // Complex value - format with parentheses
1084            format!("{}", value)
1085        }
1086    }
1087}
1088
1089// Tuple conversion implementations for ergonomic link creation
1090// These implementations allow creating links using Rust tuple syntax
1091//
1092// The macro generates From implementations for tuples of sizes 2-12.
1093// For each size, it generates 4 types of conversions:
1094// 1. All &str - first element becomes ID, rest become values
1095// 2. All String - first element becomes ID, rest become values
1096// 3. &str ID with LiNo values - first element becomes ID, LiNo elements become values
1097// 4. All LiNo - creates anonymous link (no ID) with all elements as values
1098
1099/// Macro to implement From trait for tuples converting to LiNo<String>.
1100///
1101/// This macro generates four From implementations for each tuple size:
1102/// - `(&str, &str, ...)` - First element becomes ID, rest become string values
1103/// - `(String, String, ...)` - First element becomes ID, rest become string values
1104/// - `(&str, LiNo<String>, ...)` - First element becomes ID, LiNo elements become values
1105/// - `(LiNo<String>, LiNo<String>, ...)` - Creates anonymous link with all elements as values
1106///
1107/// # Examples
1108/// ```
1109/// use links_notation::LiNo;
1110///
1111/// // 2-tuple: ("id", "value") -> (id: value)
1112/// let link: LiNo<String> = ("papa", "mama").into();
1113/// assert_eq!(format!("{}", link), "(papa: mama)");
1114///
1115/// // 3-tuple: ("id", "v1", "v2") -> (id: v1 v2)
1116/// let link: LiNo<String> = ("parent", "child1", "child2").into();
1117/// assert_eq!(format!("{}", link), "(parent: child1 child2)");
1118///
1119/// // Anonymous link from all LiNo elements
1120/// let a = LiNo::Ref("a".to_string());
1121/// let b = LiNo::Ref("b".to_string());
1122/// let link: LiNo<String> = (a, b).into();
1123/// assert_eq!(format!("{}", link), "(a b)");
1124/// ```
1125macro_rules! impl_tuple_from {
1126    // Implementation for 2-tuples
1127    (@str_tuple 2, $t0:tt, $t1:tt) => {
1128        impl From<(&str, &str)> for LiNo<String> {
1129            fn from(tuple: (&str, &str)) -> Self {
1130                LiNo::Link {
1131                    id: Some(tuple.$t0.to_string()),
1132                    values: vec![LiNo::Ref(tuple.$t1.to_string())],
1133                }
1134            }
1135        }
1136    };
1137    (@string_tuple 2, $t0:tt, $t1:tt) => {
1138        impl From<(String, String)> for LiNo<String> {
1139            fn from(tuple: (String, String)) -> Self {
1140                LiNo::Link {
1141                    id: Some(tuple.$t0),
1142                    values: vec![LiNo::Ref(tuple.$t1)],
1143                }
1144            }
1145        }
1146    };
1147    (@str_lino_tuple 2, $t0:tt, $t1:tt) => {
1148        impl From<(&str, LiNo<String>)> for LiNo<String> {
1149            fn from(tuple: (&str, LiNo<String>)) -> Self {
1150                LiNo::Link {
1151                    id: Some(tuple.$t0.to_string()),
1152                    values: vec![tuple.$t1],
1153                }
1154            }
1155        }
1156    };
1157    (@lino_tuple 2, $t0:tt, $t1:tt) => {
1158        impl From<(LiNo<String>, LiNo<String>)> for LiNo<String> {
1159            fn from(tuple: (LiNo<String>, LiNo<String>)) -> Self {
1160                LiNo::Link {
1161                    id: None,
1162                    values: vec![tuple.$t0, tuple.$t1],
1163                }
1164            }
1165        }
1166    };
1167
1168    // Implementation for 3-tuples
1169    (@str_tuple 3, $t0:tt, $t1:tt, $t2:tt) => {
1170        impl From<(&str, &str, &str)> for LiNo<String> {
1171            fn from(tuple: (&str, &str, &str)) -> Self {
1172                LiNo::Link {
1173                    id: Some(tuple.$t0.to_string()),
1174                    values: vec![LiNo::Ref(tuple.$t1.to_string()), LiNo::Ref(tuple.$t2.to_string())],
1175                }
1176            }
1177        }
1178    };
1179    (@string_tuple 3, $t0:tt, $t1:tt, $t2:tt) => {
1180        impl From<(String, String, String)> for LiNo<String> {
1181            fn from(tuple: (String, String, String)) -> Self {
1182                LiNo::Link {
1183                    id: Some(tuple.$t0),
1184                    values: vec![LiNo::Ref(tuple.$t1), LiNo::Ref(tuple.$t2)],
1185                }
1186            }
1187        }
1188    };
1189    (@str_lino_tuple 3, $t0:tt, $t1:tt, $t2:tt) => {
1190        impl From<(&str, LiNo<String>, LiNo<String>)> for LiNo<String> {
1191            fn from(tuple: (&str, LiNo<String>, LiNo<String>)) -> Self {
1192                LiNo::Link {
1193                    id: Some(tuple.$t0.to_string()),
1194                    values: vec![tuple.$t1, tuple.$t2],
1195                }
1196            }
1197        }
1198    };
1199    (@lino_tuple 3, $t0:tt, $t1:tt, $t2:tt) => {
1200        impl From<(LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1201            fn from(tuple: (LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1202                LiNo::Link {
1203                    id: None,
1204                    values: vec![tuple.$t0, tuple.$t1, tuple.$t2],
1205                }
1206            }
1207        }
1208    };
1209
1210    // Implementation for 4-tuples
1211    (@str_tuple 4, $t0:tt, $t1:tt, $t2:tt, $t3:tt) => {
1212        impl From<(&str, &str, &str, &str)> for LiNo<String> {
1213            fn from(tuple: (&str, &str, &str, &str)) -> Self {
1214                LiNo::Link {
1215                    id: Some(tuple.$t0.to_string()),
1216                    values: vec![
1217                        LiNo::Ref(tuple.$t1.to_string()),
1218                        LiNo::Ref(tuple.$t2.to_string()),
1219                        LiNo::Ref(tuple.$t3.to_string()),
1220                    ],
1221                }
1222            }
1223        }
1224    };
1225    (@string_tuple 4, $t0:tt, $t1:tt, $t2:tt, $t3:tt) => {
1226        impl From<(String, String, String, String)> for LiNo<String> {
1227            fn from(tuple: (String, String, String, String)) -> Self {
1228                LiNo::Link {
1229                    id: Some(tuple.$t0),
1230                    values: vec![LiNo::Ref(tuple.$t1), LiNo::Ref(tuple.$t2), LiNo::Ref(tuple.$t3)],
1231                }
1232            }
1233        }
1234    };
1235    (@str_lino_tuple 4, $t0:tt, $t1:tt, $t2:tt, $t3:tt) => {
1236        impl From<(&str, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1237            fn from(tuple: (&str, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1238                LiNo::Link {
1239                    id: Some(tuple.$t0.to_string()),
1240                    values: vec![tuple.$t1, tuple.$t2, tuple.$t3],
1241                }
1242            }
1243        }
1244    };
1245    (@lino_tuple 4, $t0:tt, $t1:tt, $t2:tt, $t3:tt) => {
1246        impl From<(LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1247            fn from(tuple: (LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1248                LiNo::Link {
1249                    id: None,
1250                    values: vec![tuple.$t0, tuple.$t1, tuple.$t2, tuple.$t3],
1251                }
1252            }
1253        }
1254    };
1255
1256    // Implementation for 5-tuples
1257    (@str_tuple 5, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt) => {
1258        impl From<(&str, &str, &str, &str, &str)> for LiNo<String> {
1259            fn from(tuple: (&str, &str, &str, &str, &str)) -> Self {
1260                LiNo::Link {
1261                    id: Some(tuple.$t0.to_string()),
1262                    values: vec![
1263                        LiNo::Ref(tuple.$t1.to_string()),
1264                        LiNo::Ref(tuple.$t2.to_string()),
1265                        LiNo::Ref(tuple.$t3.to_string()),
1266                        LiNo::Ref(tuple.$t4.to_string()),
1267                    ],
1268                }
1269            }
1270        }
1271    };
1272    (@string_tuple 5, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt) => {
1273        impl From<(String, String, String, String, String)> for LiNo<String> {
1274            fn from(tuple: (String, String, String, String, String)) -> Self {
1275                LiNo::Link {
1276                    id: Some(tuple.$t0),
1277                    values: vec![
1278                        LiNo::Ref(tuple.$t1),
1279                        LiNo::Ref(tuple.$t2),
1280                        LiNo::Ref(tuple.$t3),
1281                        LiNo::Ref(tuple.$t4),
1282                    ],
1283                }
1284            }
1285        }
1286    };
1287    (@str_lino_tuple 5, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt) => {
1288        impl From<(&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1289            fn from(tuple: (&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1290                LiNo::Link {
1291                    id: Some(tuple.$t0.to_string()),
1292                    values: vec![tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4],
1293                }
1294            }
1295        }
1296    };
1297    (@lino_tuple 5, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt) => {
1298        impl From<(LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1299            fn from(tuple: (LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1300                LiNo::Link {
1301                    id: None,
1302                    values: vec![tuple.$t0, tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4],
1303                }
1304            }
1305        }
1306    };
1307
1308    // Implementation for 6-tuples
1309    (@str_tuple 6, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt) => {
1310        impl From<(&str, &str, &str, &str, &str, &str)> for LiNo<String> {
1311            fn from(tuple: (&str, &str, &str, &str, &str, &str)) -> Self {
1312                LiNo::Link {
1313                    id: Some(tuple.$t0.to_string()),
1314                    values: vec![
1315                        LiNo::Ref(tuple.$t1.to_string()),
1316                        LiNo::Ref(tuple.$t2.to_string()),
1317                        LiNo::Ref(tuple.$t3.to_string()),
1318                        LiNo::Ref(tuple.$t4.to_string()),
1319                        LiNo::Ref(tuple.$t5.to_string()),
1320                    ],
1321                }
1322            }
1323        }
1324    };
1325    (@string_tuple 6, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt) => {
1326        impl From<(String, String, String, String, String, String)> for LiNo<String> {
1327            fn from(tuple: (String, String, String, String, String, String)) -> Self {
1328                LiNo::Link {
1329                    id: Some(tuple.$t0),
1330                    values: vec![
1331                        LiNo::Ref(tuple.$t1),
1332                        LiNo::Ref(tuple.$t2),
1333                        LiNo::Ref(tuple.$t3),
1334                        LiNo::Ref(tuple.$t4),
1335                        LiNo::Ref(tuple.$t5),
1336                    ],
1337                }
1338            }
1339        }
1340    };
1341    (@str_lino_tuple 6, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt) => {
1342        impl From<(&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1343            fn from(tuple: (&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1344                LiNo::Link {
1345                    id: Some(tuple.$t0.to_string()),
1346                    values: vec![tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5],
1347                }
1348            }
1349        }
1350    };
1351    (@lino_tuple 6, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt) => {
1352        impl From<(LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1353            fn from(tuple: (LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1354                LiNo::Link {
1355                    id: None,
1356                    values: vec![tuple.$t0, tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5],
1357                }
1358            }
1359        }
1360    };
1361
1362    // Implementation for 7-tuples
1363    (@str_tuple 7, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt) => {
1364        impl From<(&str, &str, &str, &str, &str, &str, &str)> for LiNo<String> {
1365            fn from(tuple: (&str, &str, &str, &str, &str, &str, &str)) -> Self {
1366                LiNo::Link {
1367                    id: Some(tuple.$t0.to_string()),
1368                    values: vec![
1369                        LiNo::Ref(tuple.$t1.to_string()),
1370                        LiNo::Ref(tuple.$t2.to_string()),
1371                        LiNo::Ref(tuple.$t3.to_string()),
1372                        LiNo::Ref(tuple.$t4.to_string()),
1373                        LiNo::Ref(tuple.$t5.to_string()),
1374                        LiNo::Ref(tuple.$t6.to_string()),
1375                    ],
1376                }
1377            }
1378        }
1379    };
1380    (@string_tuple 7, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt) => {
1381        impl From<(String, String, String, String, String, String, String)> for LiNo<String> {
1382            fn from(tuple: (String, String, String, String, String, String, String)) -> Self {
1383                LiNo::Link {
1384                    id: Some(tuple.$t0),
1385                    values: vec![
1386                        LiNo::Ref(tuple.$t1),
1387                        LiNo::Ref(tuple.$t2),
1388                        LiNo::Ref(tuple.$t3),
1389                        LiNo::Ref(tuple.$t4),
1390                        LiNo::Ref(tuple.$t5),
1391                        LiNo::Ref(tuple.$t6),
1392                    ],
1393                }
1394            }
1395        }
1396    };
1397    (@str_lino_tuple 7, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt) => {
1398        impl From<(&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1399            fn from(tuple: (&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1400                LiNo::Link {
1401                    id: Some(tuple.$t0.to_string()),
1402                    values: vec![tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5, tuple.$t6],
1403                }
1404            }
1405        }
1406    };
1407    (@lino_tuple 7, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt) => {
1408        impl From<(LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1409            fn from(tuple: (LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1410                LiNo::Link {
1411                    id: None,
1412                    values: vec![tuple.$t0, tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5, tuple.$t6],
1413                }
1414            }
1415        }
1416    };
1417
1418    // Implementation for 8-tuples
1419    (@str_tuple 8, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt) => {
1420        impl From<(&str, &str, &str, &str, &str, &str, &str, &str)> for LiNo<String> {
1421            fn from(tuple: (&str, &str, &str, &str, &str, &str, &str, &str)) -> Self {
1422                LiNo::Link {
1423                    id: Some(tuple.$t0.to_string()),
1424                    values: vec![
1425                        LiNo::Ref(tuple.$t1.to_string()),
1426                        LiNo::Ref(tuple.$t2.to_string()),
1427                        LiNo::Ref(tuple.$t3.to_string()),
1428                        LiNo::Ref(tuple.$t4.to_string()),
1429                        LiNo::Ref(tuple.$t5.to_string()),
1430                        LiNo::Ref(tuple.$t6.to_string()),
1431                        LiNo::Ref(tuple.$t7.to_string()),
1432                    ],
1433                }
1434            }
1435        }
1436    };
1437    (@string_tuple 8, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt) => {
1438        impl From<(String, String, String, String, String, String, String, String)> for LiNo<String> {
1439            fn from(tuple: (String, String, String, String, String, String, String, String)) -> Self {
1440                LiNo::Link {
1441                    id: Some(tuple.$t0),
1442                    values: vec![
1443                        LiNo::Ref(tuple.$t1),
1444                        LiNo::Ref(tuple.$t2),
1445                        LiNo::Ref(tuple.$t3),
1446                        LiNo::Ref(tuple.$t4),
1447                        LiNo::Ref(tuple.$t5),
1448                        LiNo::Ref(tuple.$t6),
1449                        LiNo::Ref(tuple.$t7),
1450                    ],
1451                }
1452            }
1453        }
1454    };
1455    (@str_lino_tuple 8, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt) => {
1456        impl From<(&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1457            fn from(tuple: (&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1458                LiNo::Link {
1459                    id: Some(tuple.$t0.to_string()),
1460                    values: vec![tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5, tuple.$t6, tuple.$t7],
1461                }
1462            }
1463        }
1464    };
1465    (@lino_tuple 8, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt) => {
1466        impl From<(LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1467            fn from(tuple: (LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1468                LiNo::Link {
1469                    id: None,
1470                    values: vec![tuple.$t0, tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5, tuple.$t6, tuple.$t7],
1471                }
1472            }
1473        }
1474    };
1475
1476    // Implementation for 9-tuples
1477    (@str_tuple 9, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt) => {
1478        impl From<(&str, &str, &str, &str, &str, &str, &str, &str, &str)> for LiNo<String> {
1479            fn from(tuple: (&str, &str, &str, &str, &str, &str, &str, &str, &str)) -> Self {
1480                LiNo::Link {
1481                    id: Some(tuple.$t0.to_string()),
1482                    values: vec![
1483                        LiNo::Ref(tuple.$t1.to_string()),
1484                        LiNo::Ref(tuple.$t2.to_string()),
1485                        LiNo::Ref(tuple.$t3.to_string()),
1486                        LiNo::Ref(tuple.$t4.to_string()),
1487                        LiNo::Ref(tuple.$t5.to_string()),
1488                        LiNo::Ref(tuple.$t6.to_string()),
1489                        LiNo::Ref(tuple.$t7.to_string()),
1490                        LiNo::Ref(tuple.$t8.to_string()),
1491                    ],
1492                }
1493            }
1494        }
1495    };
1496    (@string_tuple 9, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt) => {
1497        impl From<(String, String, String, String, String, String, String, String, String)> for LiNo<String> {
1498            fn from(tuple: (String, String, String, String, String, String, String, String, String)) -> Self {
1499                LiNo::Link {
1500                    id: Some(tuple.$t0),
1501                    values: vec![
1502                        LiNo::Ref(tuple.$t1),
1503                        LiNo::Ref(tuple.$t2),
1504                        LiNo::Ref(tuple.$t3),
1505                        LiNo::Ref(tuple.$t4),
1506                        LiNo::Ref(tuple.$t5),
1507                        LiNo::Ref(tuple.$t6),
1508                        LiNo::Ref(tuple.$t7),
1509                        LiNo::Ref(tuple.$t8),
1510                    ],
1511                }
1512            }
1513        }
1514    };
1515    (@str_lino_tuple 9, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt) => {
1516        impl From<(&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1517            fn from(tuple: (&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1518                LiNo::Link {
1519                    id: Some(tuple.$t0.to_string()),
1520                    values: vec![tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5, tuple.$t6, tuple.$t7, tuple.$t8],
1521                }
1522            }
1523        }
1524    };
1525    (@lino_tuple 9, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt) => {
1526        impl From<(LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1527            fn from(tuple: (LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1528                LiNo::Link {
1529                    id: None,
1530                    values: vec![tuple.$t0, tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5, tuple.$t6, tuple.$t7, tuple.$t8],
1531                }
1532            }
1533        }
1534    };
1535
1536    // Implementation for 10-tuples
1537    (@str_tuple 10, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt, $t9:tt) => {
1538        impl From<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str)> for LiNo<String> {
1539            fn from(tuple: (&str, &str, &str, &str, &str, &str, &str, &str, &str, &str)) -> Self {
1540                LiNo::Link {
1541                    id: Some(tuple.$t0.to_string()),
1542                    values: vec![
1543                        LiNo::Ref(tuple.$t1.to_string()),
1544                        LiNo::Ref(tuple.$t2.to_string()),
1545                        LiNo::Ref(tuple.$t3.to_string()),
1546                        LiNo::Ref(tuple.$t4.to_string()),
1547                        LiNo::Ref(tuple.$t5.to_string()),
1548                        LiNo::Ref(tuple.$t6.to_string()),
1549                        LiNo::Ref(tuple.$t7.to_string()),
1550                        LiNo::Ref(tuple.$t8.to_string()),
1551                        LiNo::Ref(tuple.$t9.to_string()),
1552                    ],
1553                }
1554            }
1555        }
1556    };
1557    (@string_tuple 10, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt, $t9:tt) => {
1558        impl From<(String, String, String, String, String, String, String, String, String, String)> for LiNo<String> {
1559            fn from(tuple: (String, String, String, String, String, String, String, String, String, String)) -> Self {
1560                LiNo::Link {
1561                    id: Some(tuple.$t0),
1562                    values: vec![
1563                        LiNo::Ref(tuple.$t1),
1564                        LiNo::Ref(tuple.$t2),
1565                        LiNo::Ref(tuple.$t3),
1566                        LiNo::Ref(tuple.$t4),
1567                        LiNo::Ref(tuple.$t5),
1568                        LiNo::Ref(tuple.$t6),
1569                        LiNo::Ref(tuple.$t7),
1570                        LiNo::Ref(tuple.$t8),
1571                        LiNo::Ref(tuple.$t9),
1572                    ],
1573                }
1574            }
1575        }
1576    };
1577    (@str_lino_tuple 10, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt, $t9:tt) => {
1578        impl From<(&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1579            fn from(tuple: (&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1580                LiNo::Link {
1581                    id: Some(tuple.$t0.to_string()),
1582                    values: vec![tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5, tuple.$t6, tuple.$t7, tuple.$t8, tuple.$t9],
1583                }
1584            }
1585        }
1586    };
1587    (@lino_tuple 10, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt, $t9:tt) => {
1588        impl From<(LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1589            fn from(tuple: (LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1590                LiNo::Link {
1591                    id: None,
1592                    values: vec![tuple.$t0, tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5, tuple.$t6, tuple.$t7, tuple.$t8, tuple.$t9],
1593                }
1594            }
1595        }
1596    };
1597
1598    // Implementation for 11-tuples
1599    (@str_tuple 11, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt, $t9:tt, $t10:tt) => {
1600        impl From<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str, &str)> for LiNo<String> {
1601            fn from(tuple: (&str, &str, &str, &str, &str, &str, &str, &str, &str, &str, &str)) -> Self {
1602                LiNo::Link {
1603                    id: Some(tuple.$t0.to_string()),
1604                    values: vec![
1605                        LiNo::Ref(tuple.$t1.to_string()),
1606                        LiNo::Ref(tuple.$t2.to_string()),
1607                        LiNo::Ref(tuple.$t3.to_string()),
1608                        LiNo::Ref(tuple.$t4.to_string()),
1609                        LiNo::Ref(tuple.$t5.to_string()),
1610                        LiNo::Ref(tuple.$t6.to_string()),
1611                        LiNo::Ref(tuple.$t7.to_string()),
1612                        LiNo::Ref(tuple.$t8.to_string()),
1613                        LiNo::Ref(tuple.$t9.to_string()),
1614                        LiNo::Ref(tuple.$t10.to_string()),
1615                    ],
1616                }
1617            }
1618        }
1619    };
1620    (@string_tuple 11, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt, $t9:tt, $t10:tt) => {
1621        impl From<(String, String, String, String, String, String, String, String, String, String, String)> for LiNo<String> {
1622            fn from(tuple: (String, String, String, String, String, String, String, String, String, String, String)) -> Self {
1623                LiNo::Link {
1624                    id: Some(tuple.$t0),
1625                    values: vec![
1626                        LiNo::Ref(tuple.$t1),
1627                        LiNo::Ref(tuple.$t2),
1628                        LiNo::Ref(tuple.$t3),
1629                        LiNo::Ref(tuple.$t4),
1630                        LiNo::Ref(tuple.$t5),
1631                        LiNo::Ref(tuple.$t6),
1632                        LiNo::Ref(tuple.$t7),
1633                        LiNo::Ref(tuple.$t8),
1634                        LiNo::Ref(tuple.$t9),
1635                        LiNo::Ref(tuple.$t10),
1636                    ],
1637                }
1638            }
1639        }
1640    };
1641    (@str_lino_tuple 11, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt, $t9:tt, $t10:tt) => {
1642        impl From<(&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1643            fn from(tuple: (&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1644                LiNo::Link {
1645                    id: Some(tuple.$t0.to_string()),
1646                    values: vec![tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5, tuple.$t6, tuple.$t7, tuple.$t8, tuple.$t9, tuple.$t10],
1647                }
1648            }
1649        }
1650    };
1651    (@lino_tuple 11, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt, $t9:tt, $t10:tt) => {
1652        impl From<(LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1653            fn from(tuple: (LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1654                LiNo::Link {
1655                    id: None,
1656                    values: vec![tuple.$t0, tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5, tuple.$t6, tuple.$t7, tuple.$t8, tuple.$t9, tuple.$t10],
1657                }
1658            }
1659        }
1660    };
1661
1662    // Implementation for 12-tuples
1663    (@str_tuple 12, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt, $t9:tt, $t10:tt, $t11:tt) => {
1664        impl From<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str, &str, &str)> for LiNo<String> {
1665            fn from(tuple: (&str, &str, &str, &str, &str, &str, &str, &str, &str, &str, &str, &str)) -> Self {
1666                LiNo::Link {
1667                    id: Some(tuple.$t0.to_string()),
1668                    values: vec![
1669                        LiNo::Ref(tuple.$t1.to_string()),
1670                        LiNo::Ref(tuple.$t2.to_string()),
1671                        LiNo::Ref(tuple.$t3.to_string()),
1672                        LiNo::Ref(tuple.$t4.to_string()),
1673                        LiNo::Ref(tuple.$t5.to_string()),
1674                        LiNo::Ref(tuple.$t6.to_string()),
1675                        LiNo::Ref(tuple.$t7.to_string()),
1676                        LiNo::Ref(tuple.$t8.to_string()),
1677                        LiNo::Ref(tuple.$t9.to_string()),
1678                        LiNo::Ref(tuple.$t10.to_string()),
1679                        LiNo::Ref(tuple.$t11.to_string()),
1680                    ],
1681                }
1682            }
1683        }
1684    };
1685    (@string_tuple 12, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt, $t9:tt, $t10:tt, $t11:tt) => {
1686        impl From<(String, String, String, String, String, String, String, String, String, String, String, String)> for LiNo<String> {
1687            fn from(tuple: (String, String, String, String, String, String, String, String, String, String, String, String)) -> Self {
1688                LiNo::Link {
1689                    id: Some(tuple.$t0),
1690                    values: vec![
1691                        LiNo::Ref(tuple.$t1),
1692                        LiNo::Ref(tuple.$t2),
1693                        LiNo::Ref(tuple.$t3),
1694                        LiNo::Ref(tuple.$t4),
1695                        LiNo::Ref(tuple.$t5),
1696                        LiNo::Ref(tuple.$t6),
1697                        LiNo::Ref(tuple.$t7),
1698                        LiNo::Ref(tuple.$t8),
1699                        LiNo::Ref(tuple.$t9),
1700                        LiNo::Ref(tuple.$t10),
1701                        LiNo::Ref(tuple.$t11),
1702                    ],
1703                }
1704            }
1705        }
1706    };
1707    (@str_lino_tuple 12, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt, $t9:tt, $t10:tt, $t11:tt) => {
1708        impl From<(&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1709            fn from(tuple: (&str, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1710                LiNo::Link {
1711                    id: Some(tuple.$t0.to_string()),
1712                    values: vec![tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5, tuple.$t6, tuple.$t7, tuple.$t8, tuple.$t9, tuple.$t10, tuple.$t11],
1713                }
1714            }
1715        }
1716    };
1717    (@lino_tuple 12, $t0:tt, $t1:tt, $t2:tt, $t3:tt, $t4:tt, $t5:tt, $t6:tt, $t7:tt, $t8:tt, $t9:tt, $t10:tt, $t11:tt) => {
1718        impl From<(LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)> for LiNo<String> {
1719            fn from(tuple: (LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>, LiNo<String>)) -> Self {
1720                LiNo::Link {
1721                    id: None,
1722                    values: vec![tuple.$t0, tuple.$t1, tuple.$t2, tuple.$t3, tuple.$t4, tuple.$t5, tuple.$t6, tuple.$t7, tuple.$t8, tuple.$t9, tuple.$t10, tuple.$t11],
1723                }
1724            }
1725        }
1726    };
1727
1728    // Entry point - generates all four types for a given tuple size
1729    (2) => {
1730        impl_tuple_from!(@str_tuple 2, 0, 1);
1731        impl_tuple_from!(@string_tuple 2, 0, 1);
1732        impl_tuple_from!(@str_lino_tuple 2, 0, 1);
1733        impl_tuple_from!(@lino_tuple 2, 0, 1);
1734    };
1735    (3) => {
1736        impl_tuple_from!(@str_tuple 3, 0, 1, 2);
1737        impl_tuple_from!(@string_tuple 3, 0, 1, 2);
1738        impl_tuple_from!(@str_lino_tuple 3, 0, 1, 2);
1739        impl_tuple_from!(@lino_tuple 3, 0, 1, 2);
1740    };
1741    (4) => {
1742        impl_tuple_from!(@str_tuple 4, 0, 1, 2, 3);
1743        impl_tuple_from!(@string_tuple 4, 0, 1, 2, 3);
1744        impl_tuple_from!(@str_lino_tuple 4, 0, 1, 2, 3);
1745        impl_tuple_from!(@lino_tuple 4, 0, 1, 2, 3);
1746    };
1747    (5) => {
1748        impl_tuple_from!(@str_tuple 5, 0, 1, 2, 3, 4);
1749        impl_tuple_from!(@string_tuple 5, 0, 1, 2, 3, 4);
1750        impl_tuple_from!(@str_lino_tuple 5, 0, 1, 2, 3, 4);
1751        impl_tuple_from!(@lino_tuple 5, 0, 1, 2, 3, 4);
1752    };
1753    (6) => {
1754        impl_tuple_from!(@str_tuple 6, 0, 1, 2, 3, 4, 5);
1755        impl_tuple_from!(@string_tuple 6, 0, 1, 2, 3, 4, 5);
1756        impl_tuple_from!(@str_lino_tuple 6, 0, 1, 2, 3, 4, 5);
1757        impl_tuple_from!(@lino_tuple 6, 0, 1, 2, 3, 4, 5);
1758    };
1759    (7) => {
1760        impl_tuple_from!(@str_tuple 7, 0, 1, 2, 3, 4, 5, 6);
1761        impl_tuple_from!(@string_tuple 7, 0, 1, 2, 3, 4, 5, 6);
1762        impl_tuple_from!(@str_lino_tuple 7, 0, 1, 2, 3, 4, 5, 6);
1763        impl_tuple_from!(@lino_tuple 7, 0, 1, 2, 3, 4, 5, 6);
1764    };
1765    (8) => {
1766        impl_tuple_from!(@str_tuple 8, 0, 1, 2, 3, 4, 5, 6, 7);
1767        impl_tuple_from!(@string_tuple 8, 0, 1, 2, 3, 4, 5, 6, 7);
1768        impl_tuple_from!(@str_lino_tuple 8, 0, 1, 2, 3, 4, 5, 6, 7);
1769        impl_tuple_from!(@lino_tuple 8, 0, 1, 2, 3, 4, 5, 6, 7);
1770    };
1771    (9) => {
1772        impl_tuple_from!(@str_tuple 9, 0, 1, 2, 3, 4, 5, 6, 7, 8);
1773        impl_tuple_from!(@string_tuple 9, 0, 1, 2, 3, 4, 5, 6, 7, 8);
1774        impl_tuple_from!(@str_lino_tuple 9, 0, 1, 2, 3, 4, 5, 6, 7, 8);
1775        impl_tuple_from!(@lino_tuple 9, 0, 1, 2, 3, 4, 5, 6, 7, 8);
1776    };
1777    (10) => {
1778        impl_tuple_from!(@str_tuple 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
1779        impl_tuple_from!(@string_tuple 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
1780        impl_tuple_from!(@str_lino_tuple 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
1781        impl_tuple_from!(@lino_tuple 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
1782    };
1783    (11) => {
1784        impl_tuple_from!(@str_tuple 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
1785        impl_tuple_from!(@string_tuple 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
1786        impl_tuple_from!(@str_lino_tuple 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
1787        impl_tuple_from!(@lino_tuple 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
1788    };
1789    (12) => {
1790        impl_tuple_from!(@str_tuple 12, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
1791        impl_tuple_from!(@string_tuple 12, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
1792        impl_tuple_from!(@str_lino_tuple 12, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
1793        impl_tuple_from!(@lino_tuple 12, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
1794    };
1795}
1796
1797// Generate implementations for tuples of sizes 2 through 12
1798// This follows the Rust standard library convention of supporting up to 12-tuples
1799impl_tuple_from!(2);
1800impl_tuple_from!(3);
1801impl_tuple_from!(4);
1802impl_tuple_from!(5);
1803impl_tuple_from!(6);
1804impl_tuple_from!(7);
1805impl_tuple_from!(8);
1806impl_tuple_from!(9);
1807impl_tuple_from!(10);
1808impl_tuple_from!(11);
1809impl_tuple_from!(12);
1810
1811// Vec-based conversions for arbitrary-length link creation
1812//
1813// These implementations provide an escape hatch for creating links with more
1814// than 12 values, or when the number of values is determined at runtime.
1815//
1816// Note: Rust does not support variadic generics (as of Rust 1.92), which means
1817// we cannot implement `From` for tuples of arbitrary length. This is a fundamental
1818// limitation of Rust's type system. The Rust standard library faces the same
1819// limitation, which is why traits like `Debug`, `Default`, `Hash`, etc. are only
1820// implemented for tuples up to 12 elements.
1821//
1822// For more information, see:
1823// - https://github.com/rust-lang/rfcs/issues/376 (Draft RFC: variadic generics)
1824// - https://github.com/rust-lang/rust/issues/10124 (RFC: variadic generics)
1825//
1826// Alternative approaches for arbitrary-length links:
1827// 1. Use the `LiNoBuilder` API for fluent construction
1828// 2. Use `LiNo::new()` or `LiNo::anonymous()` with a `Vec`
1829// 3. Use the `From<Vec<_>>` implementations below
1830
1831/// Convert a Vec of strings into an anonymous link.
1832///
1833/// # Examples
1834/// ```
1835/// use links_notation::LiNo;
1836///
1837/// // Create anonymous link from vector of any size
1838/// let values: Vec<&str> = (1..=20).map(|_| "val").collect();
1839/// let link: LiNo<String> = values.into();
1840/// ```
1841impl From<Vec<&str>> for LiNo<String> {
1842    fn from(values: Vec<&str>) -> Self {
1843        LiNo::Link {
1844            id: None,
1845            values: values
1846                .into_iter()
1847                .map(|s| LiNo::Ref(s.to_string()))
1848                .collect(),
1849        }
1850    }
1851}
1852
1853/// Convert a Vec of Strings into an anonymous link.
1854impl From<Vec<String>> for LiNo<String> {
1855    fn from(values: Vec<String>) -> Self {
1856        LiNo::Link {
1857            id: None,
1858            values: values.into_iter().map(LiNo::Ref).collect(),
1859        }
1860    }
1861}
1862
1863/// Convert a Vec of LiNo into an anonymous link.
1864impl From<Vec<LiNo<String>>> for LiNo<String> {
1865    fn from(values: Vec<LiNo<String>>) -> Self {
1866        LiNo::Link { id: None, values }
1867    }
1868}
1869
1870/// Convert a tuple of (id, Vec<values>) into a named link.
1871///
1872/// # Examples
1873/// ```
1874/// use links_notation::LiNo;
1875///
1876/// // Create named link with arbitrary number of values
1877/// let values: Vec<&str> = vec!["v1", "v2", "v3", "v4", "v5"];
1878/// let link: LiNo<String> = ("myLink", values).into();
1879/// assert_eq!(format!("{}", link), "(myLink: v1 v2 v3 v4 v5)");
1880/// ```
1881impl From<(&str, Vec<&str>)> for LiNo<String> {
1882    fn from((id, values): (&str, Vec<&str>)) -> Self {
1883        LiNo::Link {
1884            id: Some(id.to_string()),
1885            values: values
1886                .into_iter()
1887                .map(|s| LiNo::Ref(s.to_string()))
1888                .collect(),
1889        }
1890    }
1891}
1892
1893/// Convert a tuple of (id, Vec<String>) into a named link.
1894impl From<(String, Vec<String>)> for LiNo<String> {
1895    fn from((id, values): (String, Vec<String>)) -> Self {
1896        LiNo::Link {
1897            id: Some(id),
1898            values: values.into_iter().map(LiNo::Ref).collect(),
1899        }
1900    }
1901}
1902
1903/// Convert a tuple of (id, Vec<LiNo>) into a named link.
1904impl From<(&str, Vec<LiNo<String>>)> for LiNo<String> {
1905    fn from((id, values): (&str, Vec<LiNo<String>>)) -> Self {
1906        LiNo::Link {
1907            id: Some(id.to_string()),
1908            values,
1909        }
1910    }
1911}
1912
1913/// Convert a tuple of (String id, Vec<LiNo>) into a named link.
1914impl From<(String, Vec<LiNo<String>>)> for LiNo<String> {
1915    fn from((id, values): (String, Vec<LiNo<String>>)) -> Self {
1916        LiNo::Link {
1917            id: Some(id),
1918            values,
1919        }
1920    }
1921}