Skip to main content

links_notation/
lib.rs

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