diff --git a/.gitignore b/.gitignore index 6e649549e..d28b993bc 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ macros/target site/node_modules site/public site/resources +__pycache__ diff --git a/Cargo.lock b/Cargo.lock index 02aa10976..98239ea9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,9 +134,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arbitrary" @@ -183,7 +183,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", "synstructure", ] @@ -195,7 +195,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -437,7 +437,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.90", + "syn 2.0.93", "tempfile", "toml", ] @@ -534,7 +534,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -930,7 +930,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -941,7 +941,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -1004,7 +1004,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -1062,7 +1062,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -1167,7 +1167,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -1817,7 +1817,7 @@ checksum = "edbe595006d355eaf9ae11db92707d4338cd2384d16866131cc1afdbdd35d8d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -1870,7 +1870,7 @@ dependencies = [ "quote", "regex-syntax 0.8.5", "rustc_version", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -2151,7 +2151,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -2316,7 +2316,7 @@ dependencies = [ "regex", "regex-syntax 0.7.5", "structmeta", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -2375,7 +2375,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -2438,7 +2438,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -2697,7 +2697,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -2710,7 +2710,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -3046,9 +3046,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -3066,13 +3066,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -3306,7 +3306,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -3317,7 +3317,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -3336,7 +3336,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -3349,7 +3349,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -3387,9 +3387,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" dependencies = [ "proc-macro2", "quote", @@ -3404,7 +3404,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -3537,7 +3537,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -3548,7 +3548,7 @@ checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -3765,7 +3765,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -3795,7 +3795,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", "wasm-bindgen-shared", ] @@ -3817,7 +3817,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3945,7 +3945,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", "wasmtime-component-util", "wasmtime-wit-bindgen", "wit-parser", @@ -4031,7 +4031,7 @@ checksum = "db8efb877c9e5e67239d4553bb44dd2a34ae5cfb728f3cf2c5e64439c6ca6ee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -4577,7 +4577,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -4638,6 +4638,7 @@ dependencies = [ "pyo3-file", "serde_json", "yara-x", + "yara-x-fmt", ] [[package]] @@ -4658,7 +4659,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] @@ -4678,7 +4679,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.93", ] [[package]] diff --git a/py/Cargo.toml b/py/Cargo.toml index 657612496..5d13e183c 100644 --- a/py/Cargo.toml +++ b/py/Cargo.toml @@ -16,12 +16,17 @@ doc = false crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.23.3", features = ["abi3", "abi3-py38", "extension-module"] } +pyo3 = { version = "0.23.3", features = [ + "abi3", + "abi3-py38", + "extension-module", +] } pyo3-file = "0.10.0" serde_json = { workspace = true } protobuf-json-mapping = { workspace = true } yara-x = { workspace = true, features = ["parallel-compilation"] } +yara-x-fmt = { workspace = true } [build-dependencies] -pyo3-build-config = "0.23.3" \ No newline at end of file +pyo3-build-config = "0.23.3" diff --git a/py/pyproject.toml b/py/pyproject.toml index 8d722bd40..43a90c7d9 100644 --- a/py/pyproject.toml +++ b/py/pyproject.toml @@ -5,6 +5,7 @@ build-backend = "maturin" [project] name = "yara-x" description = "Python bindings for YARA-X" +dynamic = ["version"] requires-python = ">=3.9" readme = "README.md" keywords = ["pattern-matching", "cybersecurity", "forensics", "malware", "yara"] diff --git a/py/src/lib.rs b/py/src/lib.rs index 5d9a0287e..2a55f8bb4 100644 --- a/py/src/lib.rs +++ b/py/src/lib.rs @@ -33,6 +33,81 @@ use pyo3_file::PyFileLikeObject; use ::yara_x as yrx; +/// Formats YARA rules. +#[pyclass(unsendable)] +struct Formatter { + inner: yara_x_fmt::Formatter, +} + +#[pymethods] +impl Formatter { + /// Creates a new [`Formatter`]. + /// + /// `align_metadata` allows for aligning the equals signs in metadata definitions. + /// `align_patterns` allows for aligning the equals signs in pattern definitions. + /// `indent_section_headers` allows for indenting section headers. + /// `indent_section_contents` allows for indenting section contents. + /// `indent_spaces` is the number of spaces to use for indentation. + /// `newline_before_curly_brace` controls whether a newline is inserted before a curly brace. + /// `empty_line_before_section_header` controls whether an empty line is inserted before a section header. + /// `empty_line_after_section_header` controls whether an empty line is inserted after a section header. + #[new] + #[pyo3(signature = ( + align_metadata = true, + align_patterns = true, + indent_section_headers = true, + indent_section_contents = true, + indent_spaces = 2, + newline_before_curly_brace = false, + empty_line_before_section_header = true, + empty_line_after_section_header = false + ))] + #[allow(clippy::too_many_arguments)] + fn new( + align_metadata: bool, + align_patterns: bool, + indent_section_headers: bool, + indent_section_contents: bool, + indent_spaces: u8, + newline_before_curly_brace: bool, + empty_line_before_section_header: bool, + empty_line_after_section_header: bool, + ) -> Self { + Self { + inner: yara_x_fmt::Formatter::new() + .align_metadata(align_metadata) + .align_patterns(align_patterns) + .indent_section_headers(indent_section_headers) + .indent_section_contents(indent_section_contents) + .indent_spaces(indent_spaces) + .newline_before_curly_brace(newline_before_curly_brace) + .empty_line_before_section_header( + empty_line_before_section_header, + ) + .empty_line_after_section_header( + empty_line_after_section_header, + ), + } + } + + /// Format a YARA rule + fn format(&self, input: PyObject, output: PyObject) -> PyResult<()> { + let in_buf = PyFileLikeObject::with_requirements( + input, true, false, false, false, + )?; + + let mut out_buf = PyFileLikeObject::with_requirements( + output, false, true, false, false, + )?; + + self.inner + .format(in_buf, &mut out_buf) + .map_err(|err| PyValueError::new_err(err.to_string()))?; + + Ok(()) + } +} + /// Compiles a YARA source code producing a set of compiled [`Rules`]. /// /// This function allows compiling simple rules that don't depend on external @@ -700,5 +775,6 @@ fn yara_x(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/py/tests/test_api.py b/py/tests/test_api.py index 8248d2633..4b25e3b0d 100644 --- a/py/tests/test_api.py +++ b/py/tests/test_api.py @@ -268,3 +268,13 @@ def callback(msg): scanner.console_log(callback) scanner.scan(b'') assert ok + +def test_format(): + import io + expected_output = "rule test {\n condition:\n true\n}\n" + inp = io.StringIO("rule test {condition: true}") + output = io.StringIO() + fmt = yara_x.Formatter() + fmt.format(inp, output) + result = output.getvalue() + assert result == expected_output diff --git a/py/yara_x.pyi b/py/yara_x.pyi index 420b49931..882ff0be9 100644 --- a/py/yara_x.pyi +++ b/py/yara_x.pyi @@ -1,188 +1,274 @@ import typing - class Compiler: - r""" - Compiles YARA source code producing a set of compiled [`Rules`]. - """ - - def __new__(cls, *, relaxed_re_syntax=..., error_on_slow_pattern=...): ... - - def add_source(self, src: str, origin: typing.Optional[str]) -> None: r""" - Adds a YARA source code to be compiled. - - This function may be invoked multiple times to add several sets of YARA - rules before calling [`Compiler::build`]. If the rules provided in - `src` contain errors that prevent compilation, the function will raise - an exception with the first error encountered. Additionally, the - compiler will store this error, along with any others discovered during - compilation, which can be accessed using [`Compiler::errors`]. - - Even if a previous invocation resulted in a compilation error, you can - continue calling this function. In such cases, any rules that failed to - compile will not be included in the final compiled set. - - The optional parameter `origin` allows to specify the origin of the - source code. This usually receives the path of the file from where the - code was read, but it can be any arbitrary string that conveys information - about the source code's origin. + Compiles YARA source code producing a set of compiled [`Rules`]. """ - ... - - def define_global(self, ident: str, value: typing.Any) -> None: + def new(self, relaxed_re_syntax: bool, error_on_slow_pattern: bool) -> Compiler: + r""" + Creates a new [`Compiler`]. + + The `relaxed_re_syntax` argument controls whether the compiler should + adopt a more relaxed syntax check for regular expressions, allowing + constructs that YARA-X doesn't accept by default. + + YARA-X enforces stricter regular expression syntax compared to YARA. + For instance, YARA accepts invalid escape sequences and treats them + as literal characters (e.g., \R is interpreted as a literal 'R'). It + also allows some special characters to appear unescaped, inferring + their meaning from the context (e.g., `{` and `}` in `/foo{}bar/` are + literal, but in `/foo{0,1}bar/` they form the repetition operator + `{0,1}`). + + The `error_on_slow_pattern` argument tells the compiler to treat slow + patterns as errors, instead of warnings. + """ + ... + + def add_source(self, src: str, origin: typing.Optional[str]) -> None: + r""" + Adds a YARA source code to be compiled. + + This function may be invoked multiple times to add several sets of YARA + rules before calling [`Compiler::build`]. If the rules provided in + `src` contain errors that prevent compilation, the function will raise + an exception with the first error encountered. Additionally, the + compiler will store this error, along with any others discovered during + compilation, which can be accessed using [`Compiler::errors`]. + + Even if a previous invocation resulted in a compilation error, you can + continue calling this function. In such cases, any rules that failed to + compile will not be included in the final compiled set. + + The optional parameter `origin` allows to specify the origin of the + source code. This usually receives the path of the file from where the + code was read, but it can be any arbitrary string that conveys information + about the source code's origin. + """ + ... + + def define_global(self, ident: str, value: typing.Any) -> None: + r""" + Defines a global variable and sets its initial value. + + Global variables must be defined before calling [`Compiler::add_source`] + with some YARA rule that uses the variable. The variable will retain its + initial value when the [`Rules`] are used for scanning data, however + each scanner can change the variable's value by calling + [`crate::Scanner::set_global`]. + + The type of `value` must be: bool, str, bytes, int or float. + + # Raises + + [TypeError](https://docs.python.org/3/library/exceptions.html#TypeError) + if the type of `value` is not one of the supported ones. + """ + ... + + def new_namespace(self, namespace: str) -> None: + r""" + Creates a new namespace. + + Further calls to [`Compiler::add_source`] will put the rules under the + newly created namespace. + """ + ... + + def ignore_module(self, module: str) -> None: + r""" + Tell the compiler that a YARA module is not supported. + + Import statements for unsupported modules will be ignored without + errors, but a warning will be issued. Any rule that make use of an + ignored module will be ignored, while the rest of rules that + don't rely on that module will be correctly compiled. + """ + ... + + def build(self) -> Rules: + r""" + Builds the source code previously added to the compiler. + + This function returns an instance of [`Rules`] containing all the rules + previously added with [`Compiler::add_source`] and sets the compiler + to its initial empty state. + """ + ... + + def errors(self) -> typing.Any: + r""" + Retrieves all errors generated by the compiler. + + This method returns every error encountered during the compilation, + across all invocations of [`Compiler::add_source`]. + """ + ... + + def warnings(self) -> typing.Any: + r""" + Retrieves all warnings generated by the compiler. + + This method returns every warning encountered during the compilation, + across all invocations of [`Compiler::add_source`]. + """ + ... + +class Formatter: r""" - Defines a global variable and sets its initial value. - - Global variables must be defined before calling [`Compiler::add_source`] - with some YARA rule that uses the variable. The variable will retain its - initial value when the [`Rules`] are used for scanning data, however - each scanner can change the variable's value by calling - [`crate::Scanner::set_global`]. - - The type of `value` must be: bool, str, bytes, int or float. - - # Raises - - [TypeError](https://docs.python.org/3/library/exceptions.html#TypeError) - if the type of `value` is not one of the supported ones. + Formats YARA rules. """ - ... - - def new_namespace(self, namespace: str) -> None: - r""" - Creates a new namespace. - - Further calls to [`Compiler::add_source`] will put the rules under the - newly created namespace. - """ - ... + def new( + self, + align_metadata: bool, + align_patterns: bool, + indent_section_headers: bool, + indent_section_contents: bool, + indent_spaces: int, + newline_before_curly_brace: bool, + empty_line_before_section_header: bool, + empty_line_after_section_header: bool, + ) -> Formatter: + r""" + Creates a new [`Formatter`]. + + `align_metadata` allows for aligning the equals signs in metadata definitions. + `align_patterns` allows for aligning the equals signs in pattern definitions. + `indent_section_headers` allows for indenting section headers. + `indent_section_contents` allows for indenting section contents. + `indent_spaces` is the number of spaces to use for indentation. + `newline_before_curly_brace` controls whether a newline is inserted before a curly brace. + `empty_line_before_section_header` controls whether an empty line is inserted before a section header. + `empty_line_after_section_header` controls whether an empty line is inserted after a section header. + """ + ... + + def format(self, input: typing.Any, output: typing.Any) -> str: + r""" + Format a YARA rule + """ + ... - def ignore_module(self, module: str) -> None: +class Match: r""" - Tell the compiler that a YARA module is not supported. - - Import statements for unsupported modules will be ignored without - errors, but a warning will be issued. Any rule that make use of an - ignored module will be ignored, while the rest of rules that - don't rely on that module will be correctly compiled. + Represents a match found for a pattern. """ - ... + def offset(self) -> int: + r""" + Offset where the match occurred. + """ + ... + + def length(self) -> int: + r""" + Length of the match in bytes. + """ + ... + + def xor_key(self) -> typing.Optional[int]: + r""" + XOR key used for decrypting the data if the pattern had the xor + modifier, or None if otherwise. + """ + ... - def build(self) -> Rules: +class Pattern: r""" - Builds the source code previously added to the compiler. - - This function returns an instance of [`Rules`] containing all the rules - previously added with [`Compiler::add_source`] and sets the compiler - to its initial empty state. + Represents a pattern in a YARA rule. """ - ... + def identifier(self) -> str: + r""" + Pattern identifier (e.g: '$a', '$foo'). + """ + ... + + def matches(self) -> tuple: + r""" + Matches found for this pattern. + """ + ... - def errors(self) -> typing.Any: +class Rule: r""" - Retrieves all errors generated by the compiler. - - This method returns every error encountered during the compilation, - across all invocations of [`Compiler::add_source`]. + Represents a rule that matched while scanning some data. """ - ... + def identifier(self) -> str: + r""" + Returns the rule's name. + """ + ... + + def namespace(self) -> str: + r""" + Returns the rule's namespace. + """ + ... + + def tags(self) -> tuple: + r""" + Returns the rule's tags. + """ + ... + + def metadata(self) -> tuple: + r""" + A tuple of pairs `(identifier, value)` with the metadata associated to + the rule. + """ + ... + + def patterns(self) -> tuple: + r""" + Patterns defined by the rule. + """ + ... - def warnings(self) -> typing.Any: +class Rules: r""" - Retrieves all warnings generated by the compiler. + A set of YARA rules in compiled form. - This method returns every warning encountered during the compilation, - across all invocations of [`Compiler::add_source`]. + This is the result of [`Compiler::build`]. """ - ... - - -class Match: - r""" - Represents a match found for a pattern. - """ - - offset: int - length: int - xor_key: typing.Optional[int] + def scan(self, data: bytes) -> ScanResults: + r""" + Scans in-memory data with these rules. + """ + ... + + def serialize_into(self, file: typing.Any) -> None: + r""" + Serializes the rules into a file-like object. + """ + ... + + @staticmethod + def deserialize_from(self, file: typing.Any) -> Rules: + r""" + Deserializes rules from a file-like object. + """ + ... - -class Pattern: - r""" - Represents a pattern in a YARA rule. - """ - - identifier: str - matches: tuple - - -class Rule: - r""" - Represents a rule that matched while scanning some data. - """ - - identifier: str - namespace: str - tags: tuple - metadata: tuple - patterns: tuple - - -class Rules: - r""" - A set of YARA rules in compiled form. - - This is the result of [`Compiler::build`]. - """ - - def scan(self, data: bytes) -> ScanResults: +class ScanResults: r""" - Scans in-memory data with these rules. + Results produced by a scan operation. """ - ... + def matching_rules(self) -> tuple: + r""" + Rules that matched during the scan. + """ + ... + + def module_outputs(self) -> dict: + r""" + Rules that matched during the scan. + """ + ... - def serialize_into(self, file: typing.Any) -> None: +def compile(src: str) -> Rules: r""" - Serializes the rules into a file-like object. - """ - ... + Compiles a YARA source code producing a set of compiled [`Rules`]. - @staticmethod - def deserialize_from(file: typing.Any) -> Rules: - r""" - Deserializes rules from a file-like object. + This function allows compiling simple rules that don't depend on external + variables. For more complex use cases you will need to use a [`Compiler`]. """ ... - - -class ScanResults: - r""" - Results produced by a scan operation. - """ - - matching_rules: tuple - module_outputs: dict - - -class Scanner: - r""" - Scans data with already compiled YARA rules. - - The scanner receives a set of compiled Rules and scans data with those - rules. The same scanner can be used for scanning multiple files or - in-memory data sequentially, but you need multiple scanners for scanning - in parallel. - """ - - ... - - -def compile(src: str) -> Rules: - r""" - Compiles a YARA source code producing a set of compiled [`Rules`]. - - This function allows compiling simple rules that don't depend on external - variables. For more complex use cases you will need to use a [`Compiler`]. - """ - ...