├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── bird.pkl ├── config.pkl ├── pigeon.pkl └── test.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "rusty-pkl" 7 | version = "0.1.2" 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusty-pkl" 3 | version = "0.1.2" 4 | authors = ["ladroid"] 5 | description = """ 6 | Lightweight Pkl parser for Rust 7 | """ 8 | repository = "https://github.com/ladroid/pkl-rs" 9 | edition = "2021" 10 | license = "MIT" 11 | categories = ["parsing", "parser-implementations"] 12 | keywords = ["parser", "parsing", "pkl"] 13 | readme = "README.md" 14 | exclude = ["target/"] 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [dependencies] 19 | 20 | [lib] 21 | path = "src/lib.rs" 22 | 23 | [[example]] 24 | name = "test" 25 | path = "examples/test.rs" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 ladroid 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rusty-pkl 2 | 3 | rusty-pkl is a Rust library for parsing Pkl configuration files. Pkl is a simple configuration language that supports hierarchical structures and various data types. 4 | 5 | ## Features 6 | 7 | - Parse Pkl files into a structured data representation in Rust. 8 | - Support for basic Pkl syntax, including key-value pairs, nested objects, and common data types such as strings, integers, floats, and booleans. 9 | 10 | ## Usage 11 | 12 | 1. Clone this repository to your local machine 13 | 14 | 2. Navigate to the project directory: 15 | 16 | 3. Run the parser with the desired Pkl file: 17 | 18 | ```bash 19 | cargo run --example test.rs 20 | ``` 21 | 22 | ## Example 23 | 24 | Suppose you have a Pkl file named `example.pkl` with the following content: 25 | 26 | ```pkl 27 | name = "Pkl: Configure your Systems in New Ways" 28 | attendants = 100 29 | isInteractive = true 30 | amountLearned = 13.37 31 | 32 | bird { 33 | name = "Common wood pigeon" 34 | diet = "Seeds" 35 | taxonomy { 36 | species = "Columba palumbus" 37 | } 38 | } 39 | ``` 40 | 41 | Running the parser with this file will produce structured output representing the parsed Pkl values. 42 | 43 | ## Advanced Usage 44 | 45 | You can also access specific parameters programmatically and assign them to variables using the provided functions in the library. For example, to access the `name` parameter: 46 | 47 | ```rust 48 | use pkl_rs::*; 49 | 50 | fn main() { 51 | match parse_pkl("examples\\config.pkl") { 52 | Ok(pkl_value) => { 53 | println!("Parsed Pkl value: {:?}", pkl_value); 54 | // You can further process the parsed Pkl value as needed 55 | if let Some(value) = find_parameter(&pkl_value, "name") { 56 | println!("Found name parameter: {:?}", value); 57 | } else { 58 | println!("Parameter 'name' not found."); 59 | } 60 | } 61 | Err(err) => { 62 | eprintln!("Error parsing Pkl file: {}", err); 63 | } 64 | } 65 | } 66 | ``` 67 | 68 | ## Contributing 69 | 70 | Contributions are absolutely, positively welcome and encouraged! Contributions 71 | come in many forms. You could: 72 | 73 | 1. Submit a feature request or bug report as an [issue]. 74 | 2. Ask for improved documentation as an [issue]. 75 | 3. Comment on issues that require feedback. 76 | 4. Contribute code via [pull requests]. 77 | 78 | [issue]: https://github.com/ladroid/pkl-rs/issues 79 | [pull requests]: https://github.com/ladroid/pkl-rs/pulls 80 | 81 | ## License 82 | 83 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /examples/bird.pkl: -------------------------------------------------------------------------------- 1 | # Example object with properties 2 | bird { 3 | name = "Common wood pigeon" 4 | diet = "Seeds" 5 | taxonomy { 6 | species = "Columba palumbus" 7 | } 8 | } -------------------------------------------------------------------------------- /examples/config.pkl: -------------------------------------------------------------------------------- 1 | name = "Pkl: Configure your Systems in New Ways" 2 | attendants = 100 3 | isInteractive = true 4 | amountLearned = 13.37 -------------------------------------------------------------------------------- /examples/pigeon.pkl: -------------------------------------------------------------------------------- 1 | pigeonShelter { 2 | ["bird"] { 3 | name = "Common wood pigeon" 4 | diet = "Seeds" 5 | taxonomy { 6 | species = "Columba palumbus" 7 | } 8 | } 9 | ["address"] = "355 Bird St." 10 | } 11 | 12 | birdCount { 13 | [pigeonShelter] = 42 14 | } -------------------------------------------------------------------------------- /examples/test.rs: -------------------------------------------------------------------------------- 1 | use rusty_pkl::*; 2 | 3 | fn main() { 4 | match parse_pkl("examples\\config.pkl") { 5 | Ok(pkl_value) => { 6 | println!("Parsed Pkl value: {:?}", pkl_value); 7 | // You can further process the parsed Pkl value as needed 8 | print_parameter(&pkl_value, "name"); 9 | 10 | if let Some(value) = find_parameter(&pkl_value, "name") { 11 | println!("Found name parameter: {:?}", value); 12 | } else { 13 | println!("Parameter not found."); 14 | } 15 | } 16 | Err(err) => { 17 | eprintln!("Error parsing Pkl file: {}", err); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fs; 3 | 4 | // Define a data structure to represent Pkl values 5 | #[derive(Debug, Clone)] 6 | pub enum PklValue { 7 | String(String), 8 | Integer(i64), 9 | Float(f64), 10 | Boolean(bool), 11 | Object(HashMap), 12 | Array(Vec), 13 | } 14 | 15 | // Parse Pkl file into a PklValue 16 | // Parse Pkl file into a PklValue 17 | pub fn parse_pkl(filename: &str) -> Result { 18 | let content = fs::read_to_string(filename) 19 | .map_err(|err| format!("Error reading file: {}", err))?; 20 | let mut lines = content.lines(); 21 | 22 | let root_object = parse_object(&mut lines)?; 23 | 24 | Ok(root_object) 25 | } 26 | 27 | // Parse an object from Pkl file 28 | pub fn parse_object<'a, I>(lines: &mut I) -> Result 29 | where 30 | I: Iterator, 31 | { 32 | let mut object = HashMap::new(); 33 | 34 | while let Some(line) = lines.next() { 35 | let line = line.trim(); 36 | 37 | // Skip empty lines or comments 38 | if line.is_empty() || line.starts_with("#") { 39 | continue; 40 | } 41 | 42 | // Check for nested object 43 | if line.ends_with('{') { 44 | let key = line.trim_end_matches('{').trim(); 45 | let nested_object = parse_object(lines)?; 46 | object.insert(key.to_string(), nested_object); 47 | } else if line.ends_with('}') { 48 | // End of object 49 | break; 50 | } else { 51 | // Key-value pair 52 | let parts: Vec<&str> = line.splitn(2, '=').collect(); 53 | if parts.len() != 2 { 54 | return Err("Invalid syntax".to_string()); 55 | } 56 | 57 | let key = parts[0].trim(); 58 | let value = parts[1].trim(); 59 | let parsed_value = parse_value(value)?; 60 | 61 | object.insert(key.to_string(), parsed_value); 62 | } 63 | } 64 | 65 | Ok(PklValue::Object(object)) 66 | } 67 | 68 | 69 | 70 | // Parse a value string into its appropriate PklValue 71 | pub fn parse_value(value: &str) -> Result { 72 | if value.starts_with('"') && value.ends_with('"') { 73 | Ok(PklValue::String(value[1..value.len() - 1].to_string())) 74 | } else if let Ok(integer) = value.parse::() { 75 | Ok(PklValue::Integer(integer)) 76 | } else if let Ok(float) = value.parse::() { 77 | Ok(PklValue::Float(float)) 78 | } else if value == "true" { 79 | Ok(PklValue::Boolean(true)) 80 | } else if value == "false" { 81 | Ok(PklValue::Boolean(false)) 82 | } else if value.starts_with('{') && value.ends_with('}') { 83 | // Parse nested object 84 | let inner_object_content = &value[1..value.len() - 1]; 85 | let inner_object = parse_pkl_inner_object(inner_object_content)?; 86 | Ok(inner_object) 87 | } else { 88 | Err("Unsupported value type".to_string()) 89 | } 90 | } 91 | 92 | // Parse a nested object 93 | pub fn parse_pkl_inner_object(content: &str) -> Result { 94 | let mut lines = content.lines(); 95 | let mut object = HashMap::new(); 96 | 97 | while let Some(line) = lines.next() { 98 | let line = line.trim(); 99 | 100 | // Skip empty lines or comments 101 | if line.is_empty() || line.starts_with("#") { 102 | continue; 103 | } 104 | 105 | let parts: Vec<&str> = line.splitn(2, '=').collect(); 106 | if parts.len() != 2 { 107 | return Err("Invalid syntax".to_string()); 108 | } 109 | 110 | let key = parts[0].trim(); 111 | let value = parts[1].trim(); 112 | let parsed_value = parse_value(value)?; 113 | 114 | object.insert(key.to_string(), parsed_value); 115 | } 116 | 117 | Ok(PklValue::Object(object)) 118 | } 119 | 120 | // Function to recursively print specific parameters from PklValue 121 | pub fn print_parameter(pkl_value: &PklValue, parameter: &str) { 122 | match pkl_value { 123 | PklValue::Object(object) => { 124 | // Check if the parameter exists in the current object 125 | if let Some(value) = object.get(parameter) { 126 | println!("{}: {:?}", parameter, value); 127 | } else { 128 | // If not found, recursively search through nested objects 129 | for (_, nested_value) in object { 130 | print_parameter(nested_value, parameter); 131 | } 132 | } 133 | } 134 | _ => { 135 | println!("Parameter '{}' not found.", parameter); 136 | } 137 | } 138 | } 139 | 140 | // Function to recursively find and return specific parameters from PklValue 141 | pub fn find_parameter(pkl_value: &PklValue, parameter: &str) -> Option { 142 | match pkl_value { 143 | PklValue::Object(object) => { 144 | // Check if the parameter exists in the current object 145 | if let Some(value) = object.get(parameter) { 146 | Some(value.clone()) 147 | } else { 148 | // If not found, recursively search through nested objects 149 | for (_, nested_value) in object { 150 | if let Some(found_value) = find_parameter(nested_value, parameter) { 151 | return Some(found_value); 152 | } 153 | } 154 | None 155 | } 156 | } 157 | _ => None, 158 | } 159 | } 160 | --------------------------------------------------------------------------------