Skip to main content

links_notation/
lib.rs

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