dotfiles_actions/brew/
directive.rs

1// Copyright (c) 2021-2022 Miguel Barreto and others
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.
21//! This module defines [BrewDirective].
22
23extern crate strict_yaml_rust;
24
25use crate::brew::action::BrewAction;
26#[cfg(target_os = "macos")]
27use crate::brew::action::MacAppStoreItem;
28use dotfiles_core::action::ActionParser;
29use dotfiles_core::action::SKIP_IN_CI_SETTING;
30use dotfiles_core::directive::DirectiveData;
31use dotfiles_core::error::add_directive_error_prefix;
32use dotfiles_core::error::DotfilesError;
33#[cfg(target_os = "macos")]
34use dotfiles_core::error::ErrorType;
35use dotfiles_core::settings::initialize_settings_object;
36use dotfiles_core::settings::Setting;
37use dotfiles_core::settings::Settings;
38use dotfiles_core::yaml_util::*;
39use dotfiles_core_macros::Directive;
40
41use std::marker::PhantomData;
42use std::path::Path;
43use strict_yaml_rust::StrictYaml;
44
45/// Name of the Brew directive
46pub const DIRECTIVE_NAME: &str = "brew";
47/// force casks
48pub const FORCE_CASKS_SETTING: &str = "force_casks";
49/// adopt casks to deal with previously installed apps
50pub const ADOPT_CASKS_SETTING: &str = "adopt_casks";
51
52/// The string that identifies the list of taps to install
53pub const TAP_SETTING: &str = "tap";
54/// The string that identifies the list of formulae to install
55pub const FORMULA_SETTING: &str = "formula";
56/// The string that identifies the list of casks to install
57pub const CASK_SETTING: &str = "cask";
58
59/// Initialize the defaults for the BrewDirective.
60pub fn init_directive_data() -> DirectiveData {
61  DirectiveData::from(
62    DIRECTIVE_NAME.into(),
63    initialize_settings_object(&[
64      (FORCE_CASKS_SETTING.to_owned(), Setting::Boolean(false)),
65      (ADOPT_CASKS_SETTING.to_owned(), Setting::Boolean(false)),
66      (SKIP_IN_CI_SETTING.to_owned(), Setting::Boolean(false)),
67    ]),
68  )
69}
70
71/// A directive that can build [BrewAction]s to install formulae, casks
72#[derive(Directive, Clone)]
73pub struct BrewDirective<'a> {
74  data: DirectiveData,
75  phantom_data: PhantomData<&'a DirectiveData>,
76}
77
78impl<'a> Default for BrewDirective<'a> {
79  fn default() -> BrewDirective<'a> {
80    BrewDirective::<'a> {
81      data: init_directive_data(),
82      phantom_data: PhantomData,
83    }
84  }
85}
86
87impl<'a> ActionParser<'a> for BrewDirective<'a> {
88  type ActionType = BrewAction<'a>;
89
90  fn parse_action(
91    &'a self,
92    context_settings: &Settings,
93    yaml: &StrictYaml,
94    _: &Path,
95  ) -> Result<BrewAction<'a>, DotfilesError> {
96    let force_casks = get_boolean_setting_from_yaml_or_context(
97      FORCE_CASKS_SETTING,
98      yaml,
99      context_settings,
100      self.data.defaults(),
101    )?;
102    let adopt_casks = get_boolean_setting_from_yaml_or_context(
103      ADOPT_CASKS_SETTING,
104      yaml,
105      context_settings,
106      self.data.defaults(),
107    )?;
108    let skip_in_ci = get_boolean_setting_from_yaml_or_context(
109      SKIP_IN_CI_SETTING,
110      yaml,
111      context_settings,
112      self.data.defaults(),
113    )?;
114    let taps = get_optional_string_array_from_yaml_hash(TAP_SETTING, yaml)?;
115    let formulae = get_optional_string_array_from_yaml_hash(FORMULA_SETTING, yaml)?;
116    let casks = get_optional_string_array_from_yaml_hash(CASK_SETTING, yaml)?;
117    #[cfg(target_os = "macos")]
118    let mas_apps = process_value_from_yaml_hash("mas", yaml, |mas_yaml| {
119      fold_hash_until_first_err(
120        mas_yaml,
121        Ok(Vec::<MacAppStoreItem>::new()),
122        |key, val| {
123          Ok((
124            val
125              .to_owned()
126              .into_string()
127              .ok_or(DotfilesError::from_wrong_yaml(
128                "Mac App Store app ID is not a string as expected".into(),
129                val.to_owned(),
130                StrictYaml::String("".into()),
131              ))
132              .and_then(|id| {
133                id.parse::<i64>().map_err(|_| {
134                  DotfilesError::from(
135                    format!("{id} is not a valid Mac App Store app id"),
136                    ErrorType::InconsistentConfigurationError,
137                  )
138                })
139              })?,
140            key,
141          ))
142        },
143        |mut list, item| {
144          list.push(MacAppStoreItem::from(item));
145          Ok(list)
146        },
147      )
148    })
149    .map_or_else(
150      |err| {
151        if err.is_missing_config("mas") {
152          Ok(Vec::new())
153        } else {
154          Err(err)
155        }
156      },
157      Ok,
158    )?;
159
160    Ok(BrewAction::new(
161      skip_in_ci,
162      force_casks,
163      adopt_casks,
164      taps,
165      formulae,
166      casks,
167      #[cfg(target_os = "macos")]
168      mas_apps,
169    ))
170  }
171
172  /// Parse the list of actions from yaml, in this case it's only one action so
173  /// this function only wraps [BrewDirective::parse_action]
174  fn parse_action_list(
175    &'a self,
176    context_settings: &Settings,
177    yaml: &StrictYaml,
178    current_dir: &Path,
179  ) -> Result<Vec<BrewAction<'a>>, DotfilesError> {
180    Ok(vec![add_directive_error_prefix(
181      self,
182      self.parse_action(context_settings, yaml, current_dir),
183    )?])
184  }
185}