darklua_core/rules/bundle/
mod.rs1pub(crate) mod path_require_mode;
2mod require_mode;
3
4use std::path::Path;
5
6use crate::nodes::Block;
7use crate::rules::{
8 Context, Rule, RuleConfiguration, RuleConfigurationError, RuleProcessResult, RuleProperties,
9};
10use crate::Parser;
11
12pub use require_mode::BundleRequireMode;
13use wax::Pattern;
14
15pub const BUNDLER_RULE_NAME: &str = "bundler";
16
17#[derive(Debug)]
18pub(crate) struct BundleOptions {
19 parser: Parser,
20 modules_identifier: String,
21 excludes: Option<wax::Any<'static>>,
22}
23
24impl BundleOptions {
25 fn new<'a>(
26 parser: Parser,
27 modules_identifier: impl Into<String>,
28 excludes: impl Iterator<Item = &'a str>,
29 ) -> Self {
30 let excludes: Vec<_> = excludes
31 .filter_map(|exclusion| match wax::Glob::new(exclusion) {
32 Ok(glob) => Some(glob.into_owned()),
33 Err(err) => {
34 log::warn!(
35 "unable to create exclude matcher from `{}`: {}",
36 exclusion,
37 err.to_string()
38 );
39 None
40 }
41 })
42 .collect();
43 Self {
44 parser,
45 modules_identifier: modules_identifier.into(),
46 excludes: if excludes.is_empty() {
47 None
48 } else {
49 let any_pattern = wax::any::<wax::Glob, _>(excludes)
50 .expect("exclude globs errors should be filtered and only emit a warning");
51 Some(any_pattern)
52 },
53 }
54 }
55
56 fn parser(&self) -> &Parser {
57 &self.parser
58 }
59
60 fn modules_identifier(&self) -> &str {
61 &self.modules_identifier
62 }
63
64 fn is_excluded(&self, require: &Path) -> bool {
65 self.excludes
66 .as_ref()
67 .map(|any| any.is_match(require))
68 .unwrap_or(false)
69 }
70}
71
72#[derive(Debug)]
74pub(crate) struct Bundler {
75 require_mode: BundleRequireMode,
76 options: BundleOptions,
77}
78
79impl Bundler {
80 pub(crate) fn new<'a>(
81 parser: Parser,
82 require_mode: BundleRequireMode,
83 excludes: impl Iterator<Item = &'a str>,
84 ) -> Self {
85 Self {
86 require_mode,
87 options: BundleOptions::new(parser, DEFAULT_MODULE_IDENTIFIER, excludes),
88 }
89 }
90
91 pub(crate) fn with_modules_identifier(mut self, modules_identifier: impl Into<String>) -> Self {
92 self.options.modules_identifier = modules_identifier.into();
93 self
94 }
95}
96
97impl Rule for Bundler {
98 fn process(&self, block: &mut Block, context: &Context) -> RuleProcessResult {
99 self.require_mode
100 .process_block(block, context, &self.options)
101 }
102}
103
104impl RuleConfiguration for Bundler {
105 fn configure(&mut self, _properties: RuleProperties) -> Result<(), RuleConfigurationError> {
106 Err(RuleConfigurationError::InternalUsageOnly(
107 self.get_name().to_owned(),
108 ))
109 }
110
111 fn get_name(&self) -> &'static str {
112 BUNDLER_RULE_NAME
113 }
114
115 fn serialize_to_properties(&self) -> RuleProperties {
116 RuleProperties::new()
117 }
118}
119
120const DEFAULT_MODULE_IDENTIFIER: &str = "__DARKLUA_BUNDLE_MODULES";
121
122#[cfg(test)]
123mod test {
124 use super::*;
125 use crate::rules::{require::PathRequireMode, Rule};
126
127 use insta::assert_json_snapshot;
128
129 fn new_rule() -> Bundler {
130 Bundler::new(
131 Parser::default(),
132 BundleRequireMode::default(),
133 std::iter::empty(),
134 )
135 }
136
137 fn new_rule_with_require_mode(mode: impl Into<BundleRequireMode>) -> Bundler {
138 Bundler::new(Parser::default(), mode.into(), std::iter::empty())
139 }
140
141 #[test]
146 fn serialize_default_rule() {
147 let rule: Box<dyn Rule> = Box::new(new_rule());
148
149 assert_json_snapshot!("default_bundler", rule);
150 }
151
152 #[test]
153 fn serialize_path_require_mode_with_custom_module_folder_name() {
154 let rule: Box<dyn Rule> =
155 Box::new(new_rule_with_require_mode(PathRequireMode::new("__init__")));
156
157 assert_json_snapshot!("default_bundler", rule);
158 }
159
160 #[test]
161 fn serialize_path_require_mode_with_custom_module_folder_name_and_modules_identifier() {
162 let rule: Box<dyn Rule> = Box::new(
163 new_rule_with_require_mode(PathRequireMode::new("__init__"))
164 .with_modules_identifier("_CUSTOM_VAR"),
165 );
166
167 assert_json_snapshot!("default_bundler", rule);
168 }
169
170 #[test]
171 fn serialize_with_custom_modules_identifier() {
172 let rule: Box<dyn Rule> = Box::new(new_rule().with_modules_identifier("_CUSTOM_VAR"));
173
174 assert_json_snapshot!("default_bundler", rule);
175 }
176}