Skip to content

Commit 397f25a

Browse files
committed
feat: add protols.toml to configure protols in a workspace
1 parent c1c30a0 commit 397f25a

16 files changed

+324
-70
lines changed

Cargo.lock

Lines changed: 12 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ walkdir = "2.5.0"
2626
hard-xml = "1.36.0"
2727
tempfile = "3.12.0"
2828
serde = { version = "1.0.209", features = ["derive"] }
29+
basic-toml = "0.1.9"
2930

3031
[dev-dependencies]
3132
insta = { version = "1.39.0", features = ["yaml"] }
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[config]
2+
include_paths = ["foobar", "bazbaaz"]
3+
disable_parse_diagnostics = true
4+
5+
[config.experimental]
6+
use_protoc_diagnostics = true
7+
8+
[formatter]
9+
clang_format_path = "/usr/bin/clang-format"

src/config/mod.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
pub mod workspace;
4+
5+
fn default_clang_format_path() -> String {
6+
"clang-format".to_string()
7+
}
8+
9+
#[derive(Serialize, Deserialize, Debug, Clone)]
10+
#[serde(default)]
11+
pub struct ProtolsConfig {
12+
pub config: Config,
13+
pub formatter: FormatterConfig,
14+
}
15+
16+
#[derive(Serialize, Deserialize, Debug, Clone)]
17+
pub struct FormatterConfig {
18+
#[serde(default = "default_clang_format_path")]
19+
pub clang_format_path: String,
20+
}
21+
22+
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
23+
#[serde(default)]
24+
pub struct Config {
25+
pub include_paths: Vec<String>,
26+
pub single_file_mode: bool,
27+
pub disable_parse_diagnostics: bool,
28+
pub experimental: ExperimentalConfig,
29+
}
30+
31+
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
32+
#[serde(default)]
33+
pub struct ExperimentalConfig {
34+
pub use_protoc_diagnostics: bool,
35+
}
36+
37+
impl Default for ProtolsConfig {
38+
fn default() -> Self {
39+
Self {
40+
config: Config::default(),
41+
formatter: FormatterConfig::default(),
42+
}
43+
}
44+
}
45+
46+
impl Default for FormatterConfig {
47+
fn default() -> Self {
48+
Self {
49+
clang_format_path: default_clang_format_path(),
50+
}
51+
}
52+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
source: src/config/workspace.rs
3+
expression: ws.get_config_for_uri(&inworkspace2).unwrap()
4+
---
5+
config:
6+
include_paths: []
7+
single_file_mode: false
8+
disable_parse_diagnostics: false
9+
experimental:
10+
use_protoc_diagnostics: false
11+
formatter:
12+
clang_format_path: clang-format
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
source: src/config/workspace.rs
3+
expression: ws.get_config_for_uri(&inworkspace).unwrap()
4+
---
5+
config:
6+
include_paths:
7+
- foobar
8+
- bazbaaz
9+
single_file_mode: false
10+
disable_parse_diagnostics: true
11+
experimental:
12+
use_protoc_diagnostics: true
13+
formatter:
14+
clang_format_path: /usr/bin/clang-format
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
source: src/config/workspace.rs
3+
expression: ws.get_config_for_uri(&inworkspace)
4+
---
5+
config:
6+
include_paths:
7+
- foobar
8+
- bazbaaz
9+
single_file_mode: false
10+
disable_parse_diagnostics: true
11+
experimental:
12+
use_protoc_diagnostics: true
13+
formatter:
14+
clang_format_path: /usr/bin/clang-format

src/config/workspace.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use std::{
2+
collections::{HashMap, HashSet},
3+
path::Path,
4+
};
5+
6+
use async_lsp::lsp_types::{Url, WorkspaceFolder};
7+
8+
use crate::formatter::clang::ClangFormatter;
9+
10+
use super::ProtolsConfig;
11+
12+
const CONFIG_FILE_NAME: &str = "protols.toml";
13+
14+
pub struct WorkspaceProtoConfigs {
15+
workspaces: HashSet<Url>,
16+
configs: HashMap<Url, ProtolsConfig>,
17+
formatters: HashMap<Url, ClangFormatter>,
18+
}
19+
20+
impl WorkspaceProtoConfigs {
21+
pub fn new() -> Self {
22+
Self {
23+
workspaces: Default::default(),
24+
formatters: Default::default(),
25+
configs: Default::default(),
26+
}
27+
}
28+
29+
pub fn add_workspace(&mut self, w: &WorkspaceFolder) {
30+
let Ok(wpath) = w.uri.to_file_path() else {
31+
return;
32+
};
33+
34+
let p = Path::new(&wpath).join(CONFIG_FILE_NAME);
35+
let content = std::fs::read_to_string(p).unwrap_or_default();
36+
37+
let wr: ProtolsConfig = basic_toml::from_str(&content).unwrap_or_default();
38+
let fmt = ClangFormatter::new(
39+
&wr.formatter.clang_format_path,
40+
wpath.to_str().expect("non-utf8 path"),
41+
);
42+
43+
self.workspaces.insert(w.uri.clone());
44+
self.configs.insert(w.uri.clone(), wr);
45+
self.formatters.insert(w.uri.clone(), fmt);
46+
}
47+
48+
pub fn get_config_for_uri(&self, u: &Url) -> Option<&ProtolsConfig> {
49+
self.get_workspace_for_uri(u)
50+
.and_then(|w| self.configs.get(w))
51+
}
52+
53+
pub fn get_formatter_for_uri(&self, u: &Url) -> Option<&ClangFormatter> {
54+
self.get_workspace_for_uri(u)
55+
.and_then(|w| self.formatters.get(w))
56+
}
57+
58+
pub fn get_workspace_for_uri(&self, u: &Url) -> Option<&Url> {
59+
let upath = u.to_file_path().ok()?;
60+
self.workspaces
61+
.iter()
62+
.find(|&k| upath.starts_with(k.to_file_path().unwrap()))
63+
}
64+
}
65+
66+
#[cfg(test)]
67+
mod test {
68+
use async_lsp::lsp_types::{Url, WorkspaceFolder};
69+
use insta::assert_yaml_snapshot;
70+
use tempfile::tempdir;
71+
72+
use super::WorkspaceProtoConfigs;
73+
74+
#[test]
75+
fn test_get_for_workspace() {
76+
let tmpdir = tempdir().expect("failed to create temp directory");
77+
let tmpdir2 = tempdir().expect("failed to create temp2 directory");
78+
let f = tmpdir.path().join("protols.toml");
79+
std::fs::write(f, include_str!("input/protols-valid.toml")).unwrap();
80+
81+
let mut ws = WorkspaceProtoConfigs::new();
82+
ws.add_workspace(&WorkspaceFolder {
83+
uri: Url::from_directory_path(tmpdir.path()).unwrap(),
84+
name: "Test".to_string(),
85+
});
86+
ws.add_workspace(&WorkspaceFolder {
87+
uri: Url::from_directory_path(tmpdir2.path()).unwrap(),
88+
name: "Test2".to_string(),
89+
});
90+
91+
let inworkspace = Url::from_file_path(tmpdir.path().join("foobar.proto")).unwrap();
92+
let outworkspace =
93+
Url::from_file_path(tempdir().unwrap().path().join("out.proto")).unwrap();
94+
let inworkspace2 = Url::from_file_path(tmpdir2.path().join("foobar.proto")).unwrap();
95+
96+
assert!(ws.get_config_for_uri(&inworkspace).is_some());
97+
assert!(ws.get_config_for_uri(&inworkspace2).is_some());
98+
assert!(ws.get_config_for_uri(&outworkspace).is_none());
99+
100+
assert!(ws.get_workspace_for_uri(&inworkspace).is_some());
101+
assert!(ws.get_workspace_for_uri(&inworkspace2).is_some());
102+
assert!(ws.get_workspace_for_uri(&outworkspace).is_none());
103+
104+
assert_yaml_snapshot!(ws.get_config_for_uri(&inworkspace).unwrap());
105+
assert_yaml_snapshot!(ws.get_config_for_uri(&inworkspace2).unwrap());
106+
}
107+
108+
#[test]
109+
fn test_get_formatter_for_uri() {
110+
let tmpdir = tempdir().expect("failed to create temp directory");
111+
let tmpdir2 = tempdir().expect("failed to create temp2 directory");
112+
let f = tmpdir.path().join("protols.toml");
113+
std::fs::write(f, include_str!("input/protols-valid.toml")).unwrap();
114+
115+
let mut ws = WorkspaceProtoConfigs::new();
116+
ws.add_workspace(&WorkspaceFolder {
117+
uri: Url::from_directory_path(tmpdir.path()).unwrap(),
118+
name: "Test".to_string(),
119+
});
120+
121+
ws.add_workspace(&WorkspaceFolder {
122+
uri: Url::from_directory_path(tmpdir2.path()).unwrap(),
123+
name: "Test2".to_string(),
124+
});
125+
126+
let inworkspace = Url::from_file_path(tmpdir.path().join("foobar.proto")).unwrap();
127+
let outworkspace =
128+
Url::from_file_path(tempdir().unwrap().path().join("out.proto")).unwrap();
129+
let inworkspace2 = Url::from_file_path(tmpdir2.path().join("foobar.proto")).unwrap();
130+
131+
assert!(ws.get_formatter_for_uri(&outworkspace).is_none());
132+
assert_eq!(
133+
ws.get_formatter_for_uri(&inworkspace).unwrap().path,
134+
"/usr/bin/clang-format"
135+
);
136+
assert_eq!(
137+
ws.get_formatter_for_uri(&inworkspace2).unwrap().path,
138+
"clang-format"
139+
);
140+
}
141+
}

src/formatter/clang.rs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#![allow(clippy::needless_late_init)]
22
use std::{
33
borrow::Cow,
4-
error::Error,
54
fs::File,
65
io::Write,
76
path::{Path, PathBuf},
@@ -16,8 +15,8 @@ use tempfile::{tempdir, TempDir};
1615
use super::ProtoFormatter;
1716

1817
pub struct ClangFormatter {
19-
path: String,
20-
working_dir: Option<String>,
18+
pub path: String,
19+
working_dir: String,
2120
temp_dir: TempDir,
2221
}
2322

@@ -67,15 +66,12 @@ impl<'a> Replacement<'a> {
6766
}
6867

6968
impl ClangFormatter {
70-
pub fn new(path: &str, workdir: Option<&str>) -> Result<Self, Box<dyn Error>> {
71-
let mut c = Command::new(path);
72-
c.arg("--version").status()?;
73-
74-
Ok(Self {
75-
temp_dir: tempdir()?,
76-
path: path.to_owned(),
77-
working_dir: workdir.map(ToOwned::to_owned),
78-
})
69+
pub fn new(cmd: &str, wdir: &str) -> Self {
70+
Self {
71+
temp_dir: tempdir().expect("faile to creat temp dir"),
72+
path: cmd.to_owned(),
73+
working_dir: wdir.to_owned()
74+
}
7975
}
8076

8177
fn get_temp_file_path(&self, content: &str) -> Option<PathBuf> {
@@ -87,9 +83,7 @@ impl ClangFormatter {
8783

8884
fn get_command(&self, u: &Path) -> Command {
8985
let mut c = Command::new(self.path.as_str());
90-
if let Some(wd) = self.working_dir.as_ref() {
91-
c.current_dir(wd.as_str());
92-
}
86+
c.current_dir(self.working_dir.as_str());
9387
c.args([u.to_str().unwrap(), "--output-replacements-xml"]);
9488
c
9589
}

0 commit comments

Comments
 (0)