Skip to main content

lux_cli/
format.rs

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    /// Optional path to a workspace or Lua file to format.
13    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
26// TODO: Add `PathBuf` parameter that describes what directory or file to format here.
27pub 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    // Format the rockspec
84
85    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}