1use std::path::PathBuf;
2
3use clap::Args;
4use eyre::{Context, OptionExt, Result};
5use lux_lib::project::Project;
6use path_slash::PathExt;
7use stylua_lib::Config;
8use walkdir::WalkDir;
9
10#[derive(Args)]
11pub struct Fmt {
12 workspace_or_file: Option<PathBuf>,
14
15 #[clap(default_value = "stylua")]
16 #[arg(long)]
17 backend: FmtBackend,
18}
19
20#[derive(clap::ValueEnum, Clone, Debug)]
21enum FmtBackend {
22 Stylua,
23 EmmyluaCodestyle,
24}
25
26pub fn format(args: Fmt) -> Result<()> {
28 let project = Project::current()?.ok_or_eyre(
29 "`lx fmt` can only be executed in a lux project! Run `lx new` to create one.",
30 )?;
31
32 let config: Config = std::fs::read_to_string("stylua.toml")
33 .or_else(|_| std::fs::read_to_string(".stylua.toml"))
34 .map(|config: String| toml::from_str(&config).unwrap_or_default())
35 .unwrap_or_default();
36
37 WalkDir::new(project.root().join("src"))
38 .into_iter()
39 .chain(WalkDir::new(project.root().join("lua")))
40 .chain(WalkDir::new(project.root().join("lib")))
41 .chain(WalkDir::new(project.root().join("spec")))
42 .chain(WalkDir::new(project.root().join("test")))
43 .chain(WalkDir::new(project.root().join("tests")))
44 .filter_map(Result::ok)
45 .filter(|file| {
46 args.workspace_or_file
47 .as_ref()
48 .is_none_or(|workspace_or_file| {
49 file.path().to_path_buf().starts_with(workspace_or_file)
50 })
51 })
52 .try_for_each(|file| {
53 if PathBuf::from(file.file_name())
54 .extension()
55 .is_some_and(|ext| ext == "lua")
56 {
57 let file = file.path();
58 let unformatted_code = std::fs::read_to_string(file)?;
59 let formatted_code = match args.backend {
60 FmtBackend::Stylua => stylua_lib::format_code(
61 &unformatted_code,
62 config,
63 None,
64 stylua_lib::OutputVerification::Full,
65 )
66 .context(format!("error formatting {} with stylua.", file.display()))?,
67 FmtBackend::EmmyluaCodestyle => {
68 let uri = file.to_slash_lossy().to_string();
69 emmylua_codestyle::reformat_code(
70 &unformatted_code,
71 &uri,
72 emmylua_codestyle::FormattingOptions::default(),
73 )
74 }
75 };
76
77 std::fs::write(file, formatted_code)
78 .context(format!("error writing formatted file {}.", file.display()))?
79 };
80 Ok::<_, eyre::Report>(())
81 })?;
82
83 let rockspec = project.root().join("extra.rockspec");
86
87 if rockspec.exists() {
88 let unformatted_code = std::fs::read_to_string(&rockspec)?;
89 let formatted_code = match args.backend {
90 FmtBackend::Stylua => stylua_lib::format_code(
91 &unformatted_code,
92 config,
93 None,
94 stylua_lib::OutputVerification::Full,
95 )?,
96 FmtBackend::EmmyluaCodestyle => {
97 let uri = rockspec.to_slash_lossy().to_string();
98 emmylua_codestyle::reformat_code(
99 &unformatted_code,
100 &uri,
101 emmylua_codestyle::FormattingOptions::default(),
102 )
103 }
104 };
105
106 std::fs::write(rockspec, formatted_code)?;
107 }
108
109 Ok(())
110}