red-candle 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/candle/llm.rb CHANGED
@@ -78,7 +78,7 @@ module Candle
78
78
  JSON.parse(json_content)
79
79
  rescue JSON::ParserError => e
80
80
  # Return the raw string if parsing fails
81
- warn "Warning: Generated output is not valid JSON: #{e.message}" if options[:warn_on_parse_error]
81
+ Candle.logger.warn "Generated output is not valid JSON: #{e.message}" if options[:warn_on_parse_error]
82
82
  result
83
83
  end
84
84
  end
@@ -261,7 +261,7 @@ module Candle
261
261
  if e.message.include?("No tokenizer found")
262
262
  # Auto-detect tokenizer
263
263
  detected_tokenizer = guess_tokenizer(model_id)
264
- warn "No tokenizer found in GGUF repo. Using tokenizer from: #{detected_tokenizer}"
264
+ Candle.logger.info "No tokenizer found in GGUF repo. Using tokenizer from: #{detected_tokenizer}"
265
265
  model_str = "#{model_str}@@#{detected_tokenizer}"
266
266
  _from_pretrained(model_str, device)
267
267
  else
@@ -0,0 +1,149 @@
1
+ require 'logger'
2
+
3
+ module Candle
4
+ # Logging functionality for the Red Candle gem
5
+ class << self
6
+ # Get the current logger instance
7
+ # @return [Logger] The logger instance
8
+ def logger
9
+ @logger ||= create_default_logger
10
+ end
11
+
12
+ # Set a custom logger instance
13
+ # @param custom_logger [Logger] A custom logger instance
14
+ def logger=(custom_logger)
15
+ @logger = custom_logger
16
+ end
17
+
18
+ # Configure logging with a block
19
+ # @yield [config] Configuration object
20
+ def configure_logging
21
+ config = LoggerConfig.new
22
+ yield config if block_given?
23
+ @logger = config.build_logger
24
+ end
25
+
26
+ private
27
+
28
+ # Create the default logger with CLI-friendly settings
29
+ # @return [Logger] Configured logger instance
30
+ def create_default_logger
31
+ logger = Logger.new($stderr)
32
+ logger.level = default_log_level
33
+ logger.formatter = cli_friendly_formatter
34
+ logger
35
+ end
36
+
37
+ # Determine default log level based on environment variables
38
+ # @return [Integer] Logger level constant
39
+ def default_log_level
40
+ # Support legacy CANDLE_VERBOSE for backward compatibility, but prefer explicit configuration
41
+ return Logger::DEBUG if ENV['CANDLE_VERBOSE']
42
+ Logger::WARN # CLI-friendly: only show warnings/errors by default
43
+ end
44
+
45
+ # CLI-friendly formatter that outputs just the message
46
+ # @return [Proc] Formatter proc
47
+ def cli_friendly_formatter
48
+ proc { |severity, datetime, progname, msg| "#{msg}\n" }
49
+ end
50
+ end
51
+
52
+ # Configuration helper for logger setup
53
+ class LoggerConfig
54
+ attr_accessor :level, :output, :formatter
55
+
56
+ def initialize
57
+ @level = :warn
58
+ @output = $stderr
59
+ @formatter = :simple
60
+ end
61
+
62
+ # Build a logger from the configuration
63
+ # @return [Logger] Configured logger
64
+ def build_logger
65
+ logger = Logger.new(@output)
66
+ logger.level = normalize_level(@level)
67
+ logger.formatter = build_formatter(@formatter)
68
+ logger
69
+ end
70
+
71
+ # Set log level to debug (verbose output)
72
+ def verbose!
73
+ @level = :debug
74
+ end
75
+
76
+ # Set log level to info
77
+ def info!
78
+ @level = :info
79
+ end
80
+
81
+ # Set log level to warn (default)
82
+ def quiet!
83
+ @level = :warn
84
+ end
85
+
86
+ # Set log level to error (minimal output)
87
+ def silent!
88
+ @level = :error
89
+ end
90
+
91
+ # Log to stdout instead of stderr
92
+ def log_to_stdout!
93
+ @output = $stdout
94
+ end
95
+
96
+ # Log to a file
97
+ # @param file_path [String] Path to log file
98
+ def log_to_file!(file_path)
99
+ @output = file_path
100
+ end
101
+
102
+ # Disable logging completely
103
+ def disable!
104
+ @output = File::NULL
105
+ end
106
+
107
+ private
108
+
109
+ # Convert symbol/string level to Logger constant
110
+ # @param level [Symbol, String, Integer] Log level
111
+ # @return [Integer] Logger level constant
112
+ def normalize_level(level)
113
+ case level.to_s.downcase
114
+ when 'debug' then Logger::DEBUG
115
+ when 'info' then Logger::INFO
116
+ when 'warn', 'warning' then Logger::WARN
117
+ when 'error' then Logger::ERROR
118
+ when 'fatal' then Logger::FATAL
119
+ else Logger::WARN
120
+ end
121
+ end
122
+
123
+ # Build formatter based on type
124
+ # @param formatter_type [Symbol] Type of formatter
125
+ # @return [Proc] Formatter proc
126
+ def build_formatter(formatter_type)
127
+ case formatter_type
128
+ when :simple, :cli
129
+ proc { |severity, datetime, progname, msg| "#{msg}\n" }
130
+ when :detailed
131
+ proc do |severity, datetime, progname, msg|
132
+ "[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity}: #{msg}\n"
133
+ end
134
+ when :json
135
+ require 'json'
136
+ proc do |severity, datetime, progname, msg|
137
+ JSON.generate({
138
+ timestamp: datetime.iso8601,
139
+ level: severity,
140
+ message: msg,
141
+ program: progname
142
+ }) + "\n"
143
+ end
144
+ else
145
+ proc { |severity, datetime, progname, msg| "#{msg}\n" }
146
+ end
147
+ end
148
+ end
149
+ end
data/lib/candle/ner.rb CHANGED
@@ -196,7 +196,7 @@ module Candle
196
196
  # This is especially important for Ruby < 3.2
197
197
  max_length = 1_000_000 # 1MB of text
198
198
  if text.length > max_length
199
- warn "PatternEntityRecognizer: Text truncated from #{text.length} to #{max_length} chars for safety"
199
+ Candle.logger.warn "PatternEntityRecognizer: Text truncated from #{text.length} to #{max_length} chars for safety"
200
200
  text = text[0...max_length]
201
201
  end
202
202
 
@@ -1,5 +1,5 @@
1
1
  # :nocov:
2
2
  module Candle
3
- VERSION = "1.2.2"
3
+ VERSION = "1.3.0"
4
4
  end
5
5
  # :nocov:
data/lib/candle.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require_relative "candle/logger"
1
2
  require_relative "candle/candle"
2
3
  require_relative "candle/tensor"
3
4
  require_relative "candle/device_utils"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: red-candle
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher Petersen
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-08-13 00:00:00.000000000 Z
12
+ date: 2025-09-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rb_sys
@@ -151,7 +151,9 @@ dependencies:
151
151
  - - "~>"
152
152
  - !ruby/object:Gem::Version
153
153
  version: '3.13'
154
- description: huggingface/candle for Ruby
154
+ description: Ruby gem for running state-of-the-art language models locally. Access
155
+ LLMs, embeddings, rerankers, and NER models directly from Ruby using Rust-powered
156
+ Candle with Metal/CUDA acceleration.
155
157
  email:
156
158
  - chris@petersen.io
157
159
  - 2xijok@gmail.com
@@ -204,12 +206,6 @@ files:
204
206
  - ext/candle/src/structured/vocabulary_adapter_simple_test.rs
205
207
  - ext/candle/src/tokenizer/loader.rs
206
208
  - ext/candle/src/tokenizer/mod.rs
207
- - ext/candle/target/release/build/bindgen-0f89ba23b9ca1395/out/host-target.txt
208
- - ext/candle/target/release/build/clang-sys-cac31d63c4694603/out/common.rs
209
- - ext/candle/target/release/build/clang-sys-cac31d63c4694603/out/dynamic.rs
210
- - ext/candle/target/release/build/clang-sys-cac31d63c4694603/out/macros.rs
211
- - ext/candle/target/release/build/pulp-1b95cfe377eede97/out/x86_64_asm.rs
212
- - ext/candle/target/release/build/rb-sys-f8ac4edc30ab3e53/out/bindings-0.9.116-mri-arm64-darwin24-3.3.0.rs
213
209
  - ext/candle/tests/device_tests.rs
214
210
  - ext/candle/tests/tensor_tests.rs
215
211
  - lib/candle.rb
@@ -218,13 +214,14 @@ files:
218
214
  - lib/candle/embedding_model.rb
219
215
  - lib/candle/embedding_model_type.rb
220
216
  - lib/candle/llm.rb
217
+ - lib/candle/logger.rb
221
218
  - lib/candle/ner.rb
222
219
  - lib/candle/reranker.rb
223
220
  - lib/candle/tensor.rb
224
221
  - lib/candle/tokenizer.rb
225
222
  - lib/candle/version.rb
226
223
  - lib/red-candle.rb
227
- homepage: https://github.com/assaydepot/red-candle
224
+ homepage: https://github.com/scientist-labs/red-candle
228
225
  licenses:
229
226
  - MIT
230
227
  metadata: {}
@@ -236,16 +233,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
236
233
  requirements:
237
234
  - - ">="
238
235
  - !ruby/object:Gem::Version
239
- version: 3.2.0
236
+ version: 3.1.0
240
237
  required_rubygems_version: !ruby/object:Gem::Requirement
241
238
  requirements:
242
239
  - - ">="
243
240
  - !ruby/object:Gem::Version
244
- version: 3.3.26
241
+ version: '3.3'
245
242
  requirements:
246
243
  - Rust >= 1.85
247
- rubygems_version: 3.5.3
244
+ rubygems_version: 3.3.3
248
245
  signing_key:
249
246
  specification_version: 4
250
- summary: huggingface/candle for Ruby
247
+ summary: Ruby gem for running state-of-the-art language models locally. Access LLMs,
248
+ embeddings, rerankers, and NER models directly from Ruby using Rust-powered Candle
249
+ with Metal/CUDA acceleration.
251
250
  test_files: []
@@ -1,355 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
-
3
- use std::cell::RefCell;
4
- use std::collections::HashMap;
5
- use std::env;
6
- use std::path::{Path, PathBuf};
7
- use std::process::Command;
8
-
9
- use glob::{MatchOptions, Pattern};
10
-
11
- //================================================
12
- // Commands
13
- //================================================
14
-
15
- thread_local! {
16
- /// The errors encountered by the build script while executing commands.
17
- static COMMAND_ERRORS: RefCell<HashMap<String, Vec<String>>> = RefCell::default();
18
- }
19
-
20
- /// Adds an error encountered by the build script while executing a command.
21
- fn add_command_error(name: &str, path: &str, arguments: &[&str], message: String) {
22
- COMMAND_ERRORS.with(|e| {
23
- e.borrow_mut()
24
- .entry(name.into())
25
- .or_default()
26
- .push(format!(
27
- "couldn't execute `{} {}` (path={}) ({})",
28
- name,
29
- arguments.join(" "),
30
- path,
31
- message,
32
- ))
33
- });
34
- }
35
-
36
- /// A struct that prints the errors encountered by the build script while
37
- /// executing commands when dropped (unless explictly discarded).
38
- ///
39
- /// This is handy because we only want to print these errors when the build
40
- /// script fails to link to an instance of `libclang`. For example, if
41
- /// `llvm-config` couldn't be executed but an instance of `libclang` was found
42
- /// anyway we don't want to pollute the build output with irrelevant errors.
43
- #[derive(Default)]
44
- pub struct CommandErrorPrinter {
45
- discard: bool,
46
- }
47
-
48
- impl CommandErrorPrinter {
49
- pub fn discard(mut self) {
50
- self.discard = true;
51
- }
52
- }
53
-
54
- impl Drop for CommandErrorPrinter {
55
- fn drop(&mut self) {
56
- if self.discard {
57
- return;
58
- }
59
-
60
- let errors = COMMAND_ERRORS.with(|e| e.borrow().clone());
61
-
62
- if let Some(errors) = errors.get("llvm-config") {
63
- println!(
64
- "cargo:warning=could not execute `llvm-config` one or more \
65
- times, if the LLVM_CONFIG_PATH environment variable is set to \
66
- a full path to valid `llvm-config` executable it will be used \
67
- to try to find an instance of `libclang` on your system: {}",
68
- errors
69
- .iter()
70
- .map(|e| format!("\"{}\"", e))
71
- .collect::<Vec<_>>()
72
- .join("\n "),
73
- )
74
- }
75
-
76
- if let Some(errors) = errors.get("xcode-select") {
77
- println!(
78
- "cargo:warning=could not execute `xcode-select` one or more \
79
- times, if a valid instance of this executable is on your PATH \
80
- it will be used to try to find an instance of `libclang` on \
81
- your system: {}",
82
- errors
83
- .iter()
84
- .map(|e| format!("\"{}\"", e))
85
- .collect::<Vec<_>>()
86
- .join("\n "),
87
- )
88
- }
89
- }
90
- }
91
-
92
- #[cfg(test)]
93
- lazy_static::lazy_static! {
94
- pub static ref RUN_COMMAND_MOCK: std::sync::Mutex<
95
- Option<Box<dyn Fn(&str, &str, &[&str]) -> Option<String> + Send + Sync + 'static>>,
96
- > = std::sync::Mutex::new(None);
97
- }
98
-
99
- /// Executes a command and returns the `stdout` output if the command was
100
- /// successfully executed (errors are added to `COMMAND_ERRORS`).
101
- fn run_command(name: &str, path: &str, arguments: &[&str]) -> Option<String> {
102
- #[cfg(test)]
103
- if let Some(command) = &*RUN_COMMAND_MOCK.lock().unwrap() {
104
- return command(name, path, arguments);
105
- }
106
-
107
- let output = match Command::new(path).args(arguments).output() {
108
- Ok(output) => output,
109
- Err(error) => {
110
- let message = format!("error: {}", error);
111
- add_command_error(name, path, arguments, message);
112
- return None;
113
- }
114
- };
115
-
116
- if output.status.success() {
117
- Some(String::from_utf8_lossy(&output.stdout).into_owned())
118
- } else {
119
- let message = format!("exit code: {}", output.status);
120
- add_command_error(name, path, arguments, message);
121
- None
122
- }
123
- }
124
-
125
- /// Executes the `llvm-config` command and returns the `stdout` output if the
126
- /// command was successfully executed (errors are added to `COMMAND_ERRORS`).
127
- pub fn run_llvm_config(arguments: &[&str]) -> Option<String> {
128
- let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into());
129
- run_command("llvm-config", &path, arguments)
130
- }
131
-
132
- /// Executes the `xcode-select` command and returns the `stdout` output if the
133
- /// command was successfully executed (errors are added to `COMMAND_ERRORS`).
134
- pub fn run_xcode_select(arguments: &[&str]) -> Option<String> {
135
- run_command("xcode-select", "xcode-select", arguments)
136
- }
137
-
138
- //================================================
139
- // Search Directories
140
- //================================================
141
- // These search directories are listed in order of
142
- // preference, so if multiple `libclang` instances
143
- // are found when searching matching directories,
144
- // the `libclang` instances from earlier
145
- // directories will be preferred (though version
146
- // takes precedence over location).
147
- //================================================
148
-
149
- /// `libclang` directory patterns for Haiku.
150
- const DIRECTORIES_HAIKU: &[&str] = &[
151
- "/boot/home/config/non-packaged/develop/lib",
152
- "/boot/home/config/non-packaged/lib",
153
- "/boot/system/non-packaged/develop/lib",
154
- "/boot/system/non-packaged/lib",
155
- "/boot/system/develop/lib",
156
- "/boot/system/lib",
157
- ];
158
-
159
- /// `libclang` directory patterns for Linux (and FreeBSD).
160
- const DIRECTORIES_LINUX: &[&str] = &[
161
- "/usr/local/llvm*/lib*",
162
- "/usr/local/lib*/*/*",
163
- "/usr/local/lib*/*",
164
- "/usr/local/lib*",
165
- "/usr/lib*/*/*",
166
- "/usr/lib*/*",
167
- "/usr/lib*",
168
- ];
169
-
170
- /// `libclang` directory patterns for macOS.
171
- const DIRECTORIES_MACOS: &[&str] = &[
172
- "/usr/local/opt/llvm*/lib/llvm*/lib",
173
- "/Library/Developer/CommandLineTools/usr/lib",
174
- "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib",
175
- "/usr/local/opt/llvm*/lib",
176
- ];
177
-
178
- /// `libclang` directory patterns for Windows.
179
- ///
180
- /// The boolean indicates whether the directory pattern should be used when
181
- /// compiling for an MSVC target environment.
182
- const DIRECTORIES_WINDOWS: &[(&str, bool)] = &[
183
- // LLVM + Clang can be installed using Scoop (https://scoop.sh).
184
- // Other Windows package managers install LLVM + Clang to other listed
185
- // system-wide directories.
186
- ("C:\\Users\\*\\scoop\\apps\\llvm\\current\\lib", true),
187
- ("C:\\MSYS*\\MinGW*\\lib", false),
188
- ("C:\\Program Files*\\LLVM\\lib", true),
189
- ("C:\\LLVM\\lib", true),
190
- // LLVM + Clang can be installed as a component of Visual Studio.
191
- // https://github.com/KyleMayes/clang-sys/issues/121
192
- ("C:\\Program Files*\\Microsoft Visual Studio\\*\\VC\\Tools\\Llvm\\**\\lib", true),
193
- ];
194
-
195
- /// `libclang` directory patterns for illumos
196
- const DIRECTORIES_ILLUMOS: &[&str] = &[
197
- "/opt/ooce/llvm-*/lib",
198
- "/opt/ooce/clang-*/lib",
199
- ];
200
-
201
- //================================================
202
- // Searching
203
- //================================================
204
-
205
- /// Finds the files in a directory that match one or more filename glob patterns
206
- /// and returns the paths to and filenames of those files.
207
- fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
208
- // Escape the specified directory in case it contains characters that have
209
- // special meaning in glob patterns (e.g., `[` or `]`).
210
- let directory = Pattern::escape(directory.to_str().unwrap());
211
- let directory = Path::new(&directory);
212
-
213
- // Join the escaped directory to the filename glob patterns to obtain
214
- // complete glob patterns for the files being searched for.
215
- let paths = filenames
216
- .iter()
217
- .map(|f| directory.join(f).to_str().unwrap().to_owned());
218
-
219
- // Prevent wildcards from matching path separators to ensure that the search
220
- // is limited to the specified directory.
221
- let mut options = MatchOptions::new();
222
- options.require_literal_separator = true;
223
-
224
- paths
225
- .map(|p| glob::glob_with(&p, options))
226
- .filter_map(Result::ok)
227
- .flatten()
228
- .filter_map(|p| {
229
- let path = p.ok()?;
230
- let filename = path.file_name()?.to_str().unwrap();
231
-
232
- // The `libclang_shared` library has been renamed to `libclang-cpp`
233
- // in Clang 10. This can cause instances of this library (e.g.,
234
- // `libclang-cpp.so.10`) to be matched by patterns looking for
235
- // instances of `libclang`.
236
- if filename.contains("-cpp.") {
237
- return None;
238
- }
239
-
240
- Some((path.parent().unwrap().to_owned(), filename.into()))
241
- })
242
- .collect::<Vec<_>>()
243
- }
244
-
245
- /// Finds the files in a directory (and any relevant sibling directories) that
246
- /// match one or more filename glob patterns and returns the paths to and
247
- /// filenames of those files.
248
- fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
249
- let mut results = search_directory(directory, filenames);
250
-
251
- // On Windows, `libclang.dll` is usually found in the LLVM `bin` directory
252
- // while `libclang.lib` is usually found in the LLVM `lib` directory. To
253
- // keep things consistent with other platforms, only LLVM `lib` directories
254
- // are included in the backup search directory globs so we need to search
255
- // the LLVM `bin` directory here.
256
- if target_os!("windows") && directory.ends_with("lib") {
257
- let sibling = directory.parent().unwrap().join("bin");
258
- results.extend(search_directory(&sibling, filenames));
259
- }
260
-
261
- results
262
- }
263
-
264
- /// Finds the `libclang` static or dynamic libraries matching one or more
265
- /// filename glob patterns and returns the paths to and filenames of those files.
266
- pub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec<(PathBuf, String)> {
267
- // Search only the path indicated by the relevant environment variable
268
- // (e.g., `LIBCLANG_PATH`) if it is set.
269
- if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) {
270
- // Check if the path is a matching file.
271
- if let Some(parent) = path.parent() {
272
- let filename = path.file_name().unwrap().to_str().unwrap();
273
- let libraries = search_directories(parent, filenames);
274
- if libraries.iter().any(|(_, f)| f == filename) {
275
- return vec![(parent.into(), filename.into())];
276
- }
277
- }
278
-
279
- // Check if the path is directory containing a matching file.
280
- return search_directories(&path, filenames);
281
- }
282
-
283
- let mut found = vec![];
284
-
285
- // Search the `bin` and `lib` directories in the directory returned by
286
- // `llvm-config --prefix`.
287
- if let Some(output) = run_llvm_config(&["--prefix"]) {
288
- let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
289
- found.extend(search_directories(&directory.join("bin"), filenames));
290
- found.extend(search_directories(&directory.join("lib"), filenames));
291
- found.extend(search_directories(&directory.join("lib64"), filenames));
292
- }
293
-
294
- // Search the toolchain directory in the directory returned by
295
- // `xcode-select --print-path`.
296
- if target_os!("macos") {
297
- if let Some(output) = run_xcode_select(&["--print-path"]) {
298
- let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
299
- let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib");
300
- found.extend(search_directories(&directory, filenames));
301
- }
302
- }
303
-
304
- // Search the directories in the `LD_LIBRARY_PATH` environment variable.
305
- if let Ok(path) = env::var("LD_LIBRARY_PATH") {
306
- for directory in env::split_paths(&path) {
307
- found.extend(search_directories(&directory, filenames));
308
- }
309
- }
310
-
311
- // Determine the `libclang` directory patterns.
312
- let directories: Vec<&str> = if target_os!("haiku") {
313
- DIRECTORIES_HAIKU.into()
314
- } else if target_os!("linux") || target_os!("freebsd") {
315
- DIRECTORIES_LINUX.into()
316
- } else if target_os!("macos") {
317
- DIRECTORIES_MACOS.into()
318
- } else if target_os!("windows") {
319
- let msvc = target_env!("msvc");
320
- DIRECTORIES_WINDOWS
321
- .iter()
322
- .filter(|d| d.1 || !msvc)
323
- .map(|d| d.0)
324
- .collect()
325
- } else if target_os!("illumos") {
326
- DIRECTORIES_ILLUMOS.into()
327
- } else {
328
- vec![]
329
- };
330
-
331
- // We use temporary directories when testing the build script so we'll
332
- // remove the prefixes that make the directories absolute.
333
- let directories = if test!() {
334
- directories
335
- .iter()
336
- .map(|d| d.strip_prefix('/').or_else(|| d.strip_prefix("C:\\")).unwrap_or(d))
337
- .collect::<Vec<_>>()
338
- } else {
339
- directories
340
- };
341
-
342
- // Search the directories provided by the `libclang` directory patterns.
343
- let mut options = MatchOptions::new();
344
- options.case_sensitive = false;
345
- options.require_literal_separator = true;
346
- for directory in directories.iter() {
347
- if let Ok(directories) = glob::glob_with(directory, options) {
348
- for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) {
349
- found.extend(search_directories(&directory, filenames));
350
- }
351
- }
352
- }
353
-
354
- found
355
- }