Skip to main content

linguist_types/
lib.rs

1use serde::{Deserialize, Deserializer, Serialize};
2use std::collections::HashMap;
3
4/// Represents the type of a language as defined in languages.yml
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "lowercase")]
7pub enum LanguageType {
8    Data,
9    Programming,
10    Markup,
11    Prose,
12}
13
14/// Represents a single language definition from languages.yml
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct Language {
17    /// Type of language: data, programming, markup, or prose (required)
18    #[serde(rename = "type")]
19    pub language_type: LanguageType,
20
21    /// Ace editor mode used for syntax highlighting (required)
22    pub ace_mode: String,
23
24    /// TextMate scope for the language (required)
25    pub tm_scope: String,
26
27    /// Unique identifier used internally by GitHub (required)
28    pub language_id: i64,
29
30    /// List of associated file extensions
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub extensions: Option<Vec<String>>,
33
34    /// List of associated filenames
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub filenames: Option<Vec<String>>,
37
38    /// Additional aliases for the language
39    #[serde(default, skip_serializing_if = "Option::is_none")]
40    pub aliases: Option<Vec<String>>,
41
42    /// List of programs that execute the language
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub interpreters: Option<Vec<String>>,
45
46    /// CSS color code representing the language (format: "#RRGGBB")
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub color: Option<String>,
49
50    /// CodeMirror 5 mode for editing
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    pub codemirror_mode: Option<String>,
53
54    /// MIME media-type used by CodeMirror 5
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub codemirror_mime_type: Option<String>,
57
58    /// Name of the parent language (for grouping statistics)
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub group: Option<String>,
61
62    /// Filesystem-safe name for the language
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub fs_name: Option<String>,
65
66    /// Enable soft line-wrapping
67    #[serde(default, skip_serializing_if = "Option::is_none")]
68    pub wrap: Option<bool>,
69}
70
71/// The root type representing all languages in languages.yml
72/// Maps language names to their Language definitions
73pub type Languages = HashMap<String, Language>;
74
75// ============================================================================
76// Heuristics types
77// ============================================================================
78
79/// Custom deserializer for Option<Vec<String>> that accepts either a single string or an array
80fn deserialize_optional_string_or_vec<'de, D>(
81    deserializer: D,
82) -> Result<Option<Vec<String>>, D::Error>
83where
84    D: Deserializer<'de>,
85{
86    #[derive(Deserialize)]
87    #[serde(untagged)]
88    enum StringOrVec {
89        Single(String),
90        Multiple(Vec<String>),
91    }
92
93    Ok(Some(match Option::<StringOrVec>::deserialize(deserializer)? {
94        None => return Ok(None),
95        Some(StringOrVec::Single(s)) => vec![s],
96        Some(StringOrVec::Multiple(v)) => v,
97    }))
98}
99
100/// Custom deserializer for HashMap<String, Vec<String>> where values can be single strings or arrays
101fn deserialize_hashmap_string_or_vec<'de, D>(
102    deserializer: D,
103) -> Result<HashMap<String, Vec<String>>, D::Error>
104where
105    D: Deserializer<'de>,
106{
107    #[derive(Deserialize)]
108    #[serde(untagged)]
109    enum StringOrVec {
110        Single(String),
111        Multiple(Vec<String>),
112    }
113
114    let map = HashMap::<String, StringOrVec>::deserialize(deserializer)?;
115    Ok(map
116        .into_iter()
117        .map(|(k, v)| match v {
118            StringOrVec::Single(s) => (k, vec![s]),
119            StringOrVec::Multiple(v) => (k, v),
120        })
121        .collect())
122}
123
124/// A heuristic rule for disambiguating languages
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct HeuristicRule {
127    /// Language(s) to return if this rule matches
128    #[serde(
129        default,
130        skip_serializing_if = "Option::is_none",
131        deserialize_with = "deserialize_optional_string_or_vec"
132    )]
133    pub language: Option<Vec<String>>,
134
135    /// Regex pattern(s) to match (positive)
136    #[serde(
137        default,
138        skip_serializing_if = "Option::is_none",
139        deserialize_with = "deserialize_optional_string_or_vec"
140    )]
141    pub pattern: Option<Vec<String>>,
142
143    /// Regex pattern(s) to match (negative - must NOT match)
144    #[serde(
145        default,
146        skip_serializing_if = "Option::is_none",
147        deserialize_with = "deserialize_optional_string_or_vec"
148    )]
149    pub negative_pattern: Option<Vec<String>>,
150
151    /// Reference to a named pattern
152    #[serde(default, skip_serializing_if = "Option::is_none")]
153    pub named_pattern: Option<String>,
154
155    /// Multiple rules that must all match (AND condition)
156    #[serde(default, skip_serializing_if = "Option::is_none")]
157    pub and: Option<Vec<HeuristicRule>>,
158}
159
160/// A disambiguation block for a set of file extensions
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct Disambiguation {
163    /// File extensions this block applies to
164    pub extensions: Vec<String>,
165
166    /// Ordered list of rules to try
167    pub rules: Vec<HeuristicRule>,
168}
169
170/// Root structure for heuristics.yml
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct Heuristics {
173    /// List of disambiguation blocks
174    pub disambiguations: Vec<Disambiguation>,
175
176    /// Map of named patterns (regex patterns) that can be reused by rules
177    #[serde(deserialize_with = "deserialize_hashmap_string_or_vec")]
178    pub named_patterns: HashMap<String, Vec<String>>,
179}
180
181// ============================================================================
182// Vendor types
183// ============================================================================
184
185/// List of regex patterns for vendored files (vendor.yml)
186pub type VendorPatterns = Vec<String>;