Recursively import presets from provided directory (#33)

Allow a directory to be passed as a preset, in which case all files
inside the directory (recursively) are treated as presets, loaded in
lexicographical order.

This allows one to compose a system by mixing in different presets, and
easily change their order of execution.
This commit is contained in:
James McMurray 2020-03-21 06:16:15 +01:00 committed by GitHub
parent 6624f05d1e
commit 1f5b28c065
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 15 deletions

View File

@ -100,12 +100,25 @@ Preset files are simple TOML files which contain:
See the presets directory for examples.
Presets are used via the `--presets` argument (multiple preset files may be provided):
Presets are used via the `--presets` argument (multiple preset files or directories may be provided):
``` shell
sudo ALMA_USER=archie alma create /dev/disk/by-id/usb-Generic_USB_Flash_Disk-0:0 --presets ./presets/user.toml ./presets/custom_preset.toml
```
If a directory is provided, then all files and subdirectories in the directory are recursively crawled in alphanumeric order (all files must be ALMA .toml files). This allows you to use the following structure to compose many scripts in a specific order:
```
.
├── 00-add_user.toml
├── 01-xorg
│   ├── 00-install.toml
│   └── 01-config.toml
└── 02-i3
├── 00-install.toml
└── 01-copy_dotfiles.toml
```
Example preset TOML:
``` toml

View File

@ -37,7 +37,7 @@ pub struct CreateCommand {
#[structopt(parse(from_os_str))]
pub path: Option<PathBuf>,
/// Additional pacakges to install
/// Additional packages to install
#[structopt(short = "p", long = "extra-packages", value_name = "package")]
pub extra_packages: Vec<String>,

View File

@ -4,6 +4,7 @@ use serde::Deserialize;
use std::collections::HashSet;
use std::env;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
#[derive(Deserialize)]
@ -13,6 +14,21 @@ struct Preset {
environment_variables: Option<Vec<String>>,
}
fn visit_dirs(dir: &Path, filevec: &mut Vec<PathBuf>) -> Result<(), io::Error> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
visit_dirs(&path, filevec)?;
} else {
filevec.push(entry.path());
}
}
}
Ok(())
}
impl Preset {
fn load(path: &Path) -> Result<Self, Error> {
let data = fs::read_to_string(path)
@ -20,6 +36,23 @@ impl Preset {
Ok(toml::from_str(&data)
.with_context(|_| ErrorKind::Preset(format!("{}", path.display())))?)
}
fn process(
&self,
packages: &mut HashSet<String>,
scripts: &mut Vec<String>,
environment_variables: &mut HashSet<String>,
) {
if let Some(preset_packages) = &self.packages {
packages.extend(preset_packages.clone());
}
if let Some(preset_environment_variables) = &self.environment_variables {
environment_variables.extend(preset_environment_variables.clone());
}
scripts.extend(self.script.clone());
}
}
pub struct PresetsCollection {
@ -34,21 +67,31 @@ impl PresetsCollection {
let mut environment_variables = HashSet::new();
for preset in list {
let Preset {
script,
packages: preset_packages,
environment_variables: preset_environment_variables,
} = Preset::load(&preset)?;
if preset.is_dir() {
// Build vector of paths to files, then sort by path name
// Recursively load directories of preset files
let mut dir_paths: Vec<PathBuf> = Vec::new();
visit_dirs(&preset, &mut dir_paths)
.with_context(|_| ErrorKind::Preset(format!("{}", preset.display())))?;
if let Some(preset_packages) = preset_packages {
packages.extend(preset_packages);
// Order not guaranteed so we sort
// In the future may want to support numerical sort i.e. 15_... < 100_...
dir_paths.sort();
for path in dir_paths {
Preset::load(&path)?.process(
&mut packages,
&mut scripts,
&mut environment_variables,
);
}
} else {
Preset::load(&preset)?.process(
&mut packages,
&mut scripts,
&mut environment_variables,
);
}
if let Some(preset_environment_variables) = preset_environment_variables {
environment_variables.extend(preset_environment_variables);
}
scripts.extend(script);
}
let missing_envrionments: Vec<String> = environment_variables