shr-parser 0.1.5__tar.gz → 0.1.9__tar.gz

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.
@@ -1,4 +1,4 @@
1
- # This file is autogenerated by maturin v1.6.0
1
+ # This file is autogenerated by maturin v1.8.1
2
2
  # To update, run
3
3
  #
4
4
  # maturin generate-ci github
@@ -24,17 +24,17 @@ jobs:
24
24
  strategy:
25
25
  matrix:
26
26
  platform:
27
- - runner: ubuntu-latest
27
+ - runner: ubuntu-22.04
28
28
  target: x86_64
29
- - runner: ubuntu-latest
29
+ - runner: ubuntu-22.04
30
30
  target: x86
31
- - runner: ubuntu-latest
31
+ - runner: ubuntu-22.04
32
32
  target: aarch64
33
- - runner: ubuntu-latest
33
+ - runner: ubuntu-22.04
34
34
  target: armv7
35
- - runner: ubuntu-latest
35
+ - runner: ubuntu-22.04
36
36
  target: s390x
37
- - runner: ubuntu-latest
37
+ - runner: ubuntu-22.04
38
38
  target: ppc64le
39
39
  steps:
40
40
  - uses: actions/checkout@v4
@@ -59,13 +59,13 @@ jobs:
59
59
  strategy:
60
60
  matrix:
61
61
  platform:
62
- - runner: ubuntu-latest
62
+ - runner: ubuntu-22.04
63
63
  target: x86_64
64
- - runner: ubuntu-latest
64
+ - runner: ubuntu-22.04
65
65
  target: x86
66
- - runner: ubuntu-latest
66
+ - runner: ubuntu-22.04
67
67
  target: aarch64
68
- - runner: ubuntu-latest
68
+ - runner: ubuntu-22.04
69
69
  target: armv7
70
70
  steps:
71
71
  - uses: actions/checkout@v4
@@ -117,7 +117,7 @@ jobs:
117
117
  strategy:
118
118
  matrix:
119
119
  platform:
120
- - runner: macos-12
120
+ - runner: macos-13
121
121
  target: x86_64
122
122
  - runner: macos-14
123
123
  target: aarch64
@@ -156,11 +156,23 @@ jobs:
156
156
  release:
157
157
  name: Release
158
158
  runs-on: ubuntu-latest
159
- if: "startsWith(github.ref, 'refs/tags/')"
159
+ if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
160
160
  needs: [linux, musllinux, windows, macos, sdist]
161
+ permissions:
162
+ # Use to sign the release artifacts
163
+ id-token: write
164
+ # Used to upload release artifacts
165
+ contents: write
166
+ # Used to generate artifact attestation
167
+ attestations: write
161
168
  steps:
162
169
  - uses: actions/download-artifact@v4
170
+ - name: Generate artifact attestation
171
+ uses: actions/attest-build-provenance@v1
172
+ with:
173
+ subject-path: 'wheels-*/*'
163
174
  - name: Publish to PyPI
175
+ if: ${{ startsWith(github.ref, 'refs/tags/') }}
164
176
  uses: PyO3/maturin-action@v1
165
177
  env:
166
178
  MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
@@ -1,6 +1,6 @@
1
1
  # This file is automatically @generated by Cargo.
2
2
  # It is not intended for manual editing.
3
- version = 3
3
+ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "autocfg"
@@ -69,16 +69,6 @@ version = "0.2.155"
69
69
  source = "registry+https://github.com/rust-lang/crates.io-index"
70
70
  checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
71
71
 
72
- [[package]]
73
- name = "matrixmultiply"
74
- version = "0.3.8"
75
- source = "registry+https://github.com/rust-lang/crates.io-index"
76
- checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2"
77
- dependencies = [
78
- "autocfg",
79
- "rawpointer",
80
- ]
81
-
82
72
  [[package]]
83
73
  name = "memmap"
84
74
  version = "0.7.0"
@@ -98,46 +88,6 @@ dependencies = [
98
88
  "autocfg",
99
89
  ]
100
90
 
101
- [[package]]
102
- name = "ndarray"
103
- version = "0.15.6"
104
- source = "registry+https://github.com/rust-lang/crates.io-index"
105
- checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32"
106
- dependencies = [
107
- "matrixmultiply",
108
- "num-complex",
109
- "num-integer",
110
- "num-traits",
111
- "rawpointer",
112
- ]
113
-
114
- [[package]]
115
- name = "num-complex"
116
- version = "0.4.6"
117
- source = "registry+https://github.com/rust-lang/crates.io-index"
118
- checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
119
- dependencies = [
120
- "num-traits",
121
- ]
122
-
123
- [[package]]
124
- name = "num-integer"
125
- version = "0.1.46"
126
- source = "registry+https://github.com/rust-lang/crates.io-index"
127
- checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
128
- dependencies = [
129
- "num-traits",
130
- ]
131
-
132
- [[package]]
133
- name = "num-traits"
134
- version = "0.2.19"
135
- source = "registry+https://github.com/rust-lang/crates.io-index"
136
- checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
137
- dependencies = [
138
- "autocfg",
139
- ]
140
-
141
91
  [[package]]
142
92
  name = "once_cell"
143
93
  version = "1.19.0"
@@ -161,9 +111,9 @@ dependencies = [
161
111
 
162
112
  [[package]]
163
113
  name = "pyo3"
164
- version = "0.22.1"
114
+ version = "0.23.4"
165
115
  source = "registry+https://github.com/rust-lang/crates.io-index"
166
- checksum = "4e99090d12f6182924499253aaa1e73bf15c69cea8d2774c3c781e35badc3548"
116
+ checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc"
167
117
  dependencies = [
168
118
  "cfg-if",
169
119
  "indoc",
@@ -179,9 +129,9 @@ dependencies = [
179
129
 
180
130
  [[package]]
181
131
  name = "pyo3-build-config"
182
- version = "0.22.1"
132
+ version = "0.23.4"
183
133
  source = "registry+https://github.com/rust-lang/crates.io-index"
184
- checksum = "7879eb018ac754bba32cb0eec7526391c02c14a093121857ed09fbf1d1057d41"
134
+ checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7"
185
135
  dependencies = [
186
136
  "once_cell",
187
137
  "target-lexicon",
@@ -189,9 +139,9 @@ dependencies = [
189
139
 
190
140
  [[package]]
191
141
  name = "pyo3-ffi"
192
- version = "0.22.1"
142
+ version = "0.23.4"
193
143
  source = "registry+https://github.com/rust-lang/crates.io-index"
194
- checksum = "ce2baa5559a411fc1cf519295f24c34b53d5d725818bc96b5abf94762da09041"
144
+ checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d"
195
145
  dependencies = [
196
146
  "libc",
197
147
  "pyo3-build-config",
@@ -199,9 +149,9 @@ dependencies = [
199
149
 
200
150
  [[package]]
201
151
  name = "pyo3-macros"
202
- version = "0.22.1"
152
+ version = "0.23.4"
203
153
  source = "registry+https://github.com/rust-lang/crates.io-index"
204
- checksum = "049621c20a23f2def20f4fe67978d1da8d8a883d64b9c21362f3b776e254edc7"
154
+ checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7"
205
155
  dependencies = [
206
156
  "proc-macro2",
207
157
  "pyo3-macros-backend",
@@ -211,9 +161,9 @@ dependencies = [
211
161
 
212
162
  [[package]]
213
163
  name = "pyo3-macros-backend"
214
- version = "0.22.1"
164
+ version = "0.23.4"
215
165
  source = "registry+https://github.com/rust-lang/crates.io-index"
216
- checksum = "0e969ee2e025435f1819d31a275ba4bb9cbbdf3ac535227fdbd85b9322ffe144"
166
+ checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4"
217
167
  dependencies = [
218
168
  "heck",
219
169
  "proc-macro2",
@@ -231,12 +181,6 @@ dependencies = [
231
181
  "proc-macro2",
232
182
  ]
233
183
 
234
- [[package]]
235
- name = "rawpointer"
236
- version = "0.2.1"
237
- source = "registry+https://github.com/rust-lang/crates.io-index"
238
- checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
239
-
240
184
  [[package]]
241
185
  name = "rayon"
242
186
  version = "1.10.0"
@@ -279,20 +223,19 @@ dependencies = [
279
223
 
280
224
  [[package]]
281
225
  name = "shr_parser"
282
- version = "1.0.5"
226
+ version = "1.0.8"
283
227
  source = "registry+https://github.com/rust-lang/crates.io-index"
284
- checksum = "31ef87e9f8644f6debfc91b0b0e715b6a49b78ff51fbdb6cc62bdcbc2fd3e9a4"
228
+ checksum = "059ae9d14772a3811d0549afa66207858e6c13760d2c987b1fb2714719afd820"
285
229
  dependencies = [
286
230
  "byteorder",
287
231
  "memmap",
288
- "ndarray",
289
232
  "rayon",
290
233
  "serde",
291
234
  ]
292
235
 
293
236
  [[package]]
294
237
  name = "shr_parser_py"
295
- version = "0.1.5"
238
+ version = "0.1.9"
296
239
  dependencies = [
297
240
  "pyo3",
298
241
  "shr_parser",
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  name = "shr_parser_py"
3
3
  description = "Python bindings for the shr_parser Rust crate"
4
- version = "0.1.5"
4
+ version = "0.1.9"
5
5
  edition = "2021"
6
6
  license = "GPL-3.0-only"
7
7
  authors = ["Lasse Nielsen <lasse@xerrion.dk>"]
@@ -15,5 +15,5 @@ crate-type = ["cdylib"]
15
15
  name = "shr_parser"
16
16
 
17
17
  [dependencies]
18
- pyo3 = { version = "0.22.1", features = ["extension-module"] }
19
- shr_parser = "1.0.5"
18
+ pyo3 = { version = "0.23.4", features = ["extension-module"] }
19
+ shr_parser = "1.0.8"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: shr_parser
3
- Version: 0.1.5
3
+ Version: 0.1.9
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -9,10 +9,12 @@ Summary: Parses sweeps from a Signal Hound Spike file.
9
9
  Author: Lasse Nielsen
10
10
  Author-email: Lasse Nielsen <lasse@xerrion.dk>
11
11
  License: GPL-3.0-only
12
- Requires-Python: >=3.8, <=3.12
12
+ Requires-Python: >=3.8, <4
13
13
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
14
14
  Project-URL: Source Code, https://github.com/Xerrion/shr_parser_py
15
15
 
16
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/xerrion/shr_parser_py/CI.yml) ![GitHub License](https://img.shields.io/github/license/xerrion/shr_parser_py) ![Crates.io Version](https://img.shields.io/crates/v/shr_parser_py?link=https%3A%2F%2Fcrates.io%2Fcrates%2Fshr_parser_py)
17
+
16
18
  # SHR Parser Python Bindings
17
19
 
18
20
  This project provides Python bindings for the Rust-based `shr_parser` library, which is designed to parse and handle SHR files. The SHR file format includes a file header and multiple sweeps, each with its own header and data. This library uses memory mapping for efficient file reading and Rayon for parallel processing of sweeps.
@@ -78,14 +80,14 @@ A class representing a parser for SHR files.
78
80
  Here is an example usage of the SHRParser class:
79
81
 
80
82
  ```python
81
- import shr_parser
83
+ from shr_parser import SHRParser, SHRParsingType
82
84
 
83
85
  # Define the file path and parsing type
84
86
  file_path = "path/to/your/shrfile.shr"
85
- parsing_type = 0 # SHRParsingType::Peak
87
+ parsing_type = SHRParsingType.PEAK
86
88
 
87
89
  # Create a parser instance
88
- parser = shr_parser.SHRParser(file_path, parsing_type)
90
+ parser = SHRParser(file_path, parsing_type)
89
91
 
90
92
  # Get file path
91
93
  print(parser.get_file_path())
@@ -1,3 +1,5 @@
1
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/xerrion/shr_parser_py/CI.yml) ![GitHub License](https://img.shields.io/github/license/xerrion/shr_parser_py) ![Crates.io Version](https://img.shields.io/crates/v/shr_parser_py?link=https%3A%2F%2Fcrates.io%2Fcrates%2Fshr_parser_py)
2
+
1
3
  # SHR Parser Python Bindings
2
4
 
3
5
  This project provides Python bindings for the Rust-based `shr_parser` library, which is designed to parse and handle SHR files. The SHR file format includes a file header and multiple sweeps, each with its own header and data. This library uses memory mapping for efficient file reading and Rayon for parallel processing of sweeps.
@@ -63,14 +65,14 @@ A class representing a parser for SHR files.
63
65
  Here is an example usage of the SHRParser class:
64
66
 
65
67
  ```python
66
- import shr_parser
68
+ from shr_parser import SHRParser, SHRParsingType
67
69
 
68
70
  # Define the file path and parsing type
69
71
  file_path = "path/to/your/shrfile.shr"
70
- parsing_type = 0 # SHRParsingType::Peak
72
+ parsing_type = SHRParsingType.PEAK
71
73
 
72
74
  # Create a parser instance
73
- parser = shr_parser.SHRParser(file_path, parsing_type)
75
+ parser = SHRParser(file_path, parsing_type)
74
76
 
75
77
  # Get file path
76
78
  print(parser.get_file_path())
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "shr_parser"
7
- requires-python = ">=3.8,<=3.12"
7
+ requires-python = ">=3.8,<4"
8
8
  classifiers = [
9
9
  "Programming Language :: Rust",
10
10
  "Programming Language :: Python :: Implementation :: CPython",
@@ -0,0 +1,151 @@
1
+ use pyo3::prelude::*;
2
+ use pyo3::wrap_pyfunction;
3
+ use shr_parser::{SHRParser, SHRParsingType};
4
+ use std::fmt::{self, Display};
5
+ use std::path::PathBuf;
6
+
7
+ /// An enum mirroring SHRParsingType for Python.
8
+ #[pyclass(name = "SHRParsingType", eq, eq_int)]
9
+ #[derive(Clone, Copy, Debug, PartialEq)]
10
+ enum PySHRParsingType {
11
+ PEAK = 0,
12
+ MEAN = 1,
13
+ LOW = 2,
14
+ }
15
+
16
+ impl TryFrom<i32> for PySHRParsingType {
17
+ type Error = &'static str;
18
+ fn try_from(value: i32) -> Result<Self, Self::Error> {
19
+ match value {
20
+ 0 => Ok(PySHRParsingType::PEAK),
21
+ 1 => Ok(PySHRParsingType::MEAN),
22
+ 2 => Ok(PySHRParsingType::LOW),
23
+ _ => Err("Invalid value for SHRParsingType"),
24
+ }
25
+ }
26
+ }
27
+
28
+ impl TryFrom<PySHRParsingType> for SHRParsingType {
29
+ type Error = &'static str;
30
+ fn try_from(value: PySHRParsingType) -> Result<Self, Self::Error> {
31
+ match value {
32
+ PySHRParsingType::PEAK => Ok(SHRParsingType::Peak),
33
+ PySHRParsingType::MEAN => Ok(SHRParsingType::Mean),
34
+ PySHRParsingType::LOW => Ok(SHRParsingType::Low),
35
+ }
36
+ }
37
+ }
38
+
39
+ impl Display for PySHRParsingType {
40
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41
+ match self {
42
+ PySHRParsingType::PEAK => write!(f, "SHRParsingType.PEAK"),
43
+ PySHRParsingType::MEAN => write!(f, "SHRParsingType.MEAN"),
44
+ PySHRParsingType::LOW => write!(f, "SHRParsingType.LOW"),
45
+ }
46
+ }
47
+ }
48
+
49
+ /// A Python wrapper for a sweep.
50
+ #[pyclass(name = "SHRSweep")]
51
+ struct PySHRSweep {
52
+ #[pyo3(get)]
53
+ sweep_number: i32,
54
+ #[pyo3(get)]
55
+ timestamp: u64,
56
+ #[pyo3(get)]
57
+ frequency: f64,
58
+ #[pyo3(get)]
59
+ amplitude: f64,
60
+ #[pyo3(get)]
61
+ latitude: f64,
62
+ #[pyo3(get)]
63
+ longitude: f64,
64
+ }
65
+
66
+ #[pymethods]
67
+ impl PySHRSweep {
68
+ fn __repr__(&self) -> String {
69
+ format!(
70
+ "SHRSweep(sweep_number={}, timestamp={}, frequency={}, amplitude={}, Latitude={}, Longitude={})",
71
+ self.sweep_number, self.timestamp, self.frequency, self.amplitude, self.latitude, self.longitude
72
+ )
73
+ }
74
+ }
75
+
76
+ /// A Python wrapper around SHRParser.
77
+ #[pyclass(name = "SHRParser", subclass)]
78
+ struct PySHRParser {
79
+ parser: SHRParser,
80
+ parsing_type: PySHRParsingType,
81
+ }
82
+
83
+ #[pymethods]
84
+ impl PySHRParser {
85
+ #[new]
86
+ fn new(file_path: &str, parsing_type: PySHRParsingType) -> PyResult<Self> {
87
+ let shr_parsing = SHRParsingType::try_from(parsing_type)
88
+ .map_err(|e| pyo3::exceptions::PyValueError::new_err(e))?;
89
+ let parser = SHRParser::new(PathBuf::from(file_path), shr_parsing).map_err(|e| {
90
+ pyo3::exceptions::PyIOError::new_err(format!("Failed to parse SHR file: {:?}", e))
91
+ })?;
92
+ Ok(PySHRParser {
93
+ parser,
94
+ parsing_type,
95
+ })
96
+ }
97
+
98
+ fn __repr__(&self) -> String {
99
+ format!(
100
+ "SHRParser(file_path='{}', parsing_type={})",
101
+ self.parser.get_file_path().to_string_lossy(),
102
+ self.parsing_type
103
+ )
104
+ }
105
+
106
+ fn to_csv(&self, path: String) -> PyResult<()> {
107
+ self.parser.to_csv(PathBuf::from(path)).map_err(|e| {
108
+ pyo3::exceptions::PyIOError::new_err(format!("Failed to write to CSV: {:?}", e))
109
+ })
110
+ }
111
+
112
+ fn get_sweeps(&self) -> PyResult<Vec<PySHRSweep>> {
113
+ let sweeps = self.parser.get_sweeps();
114
+ Ok(sweeps
115
+ .into_iter()
116
+ .map(|sweep| PySHRSweep {
117
+ sweep_number: sweep.number,
118
+ timestamp: sweep.header.timestamp,
119
+ frequency: sweep.frequency,
120
+ amplitude: sweep.amplitude,
121
+ latitude: sweep.header.latitude,
122
+ longitude: sweep.header.longitude,
123
+ })
124
+ .collect())
125
+ }
126
+
127
+ fn get_file_header(&self) -> PyResult<String> {
128
+ let header = self.parser.get_file_header();
129
+ Ok(format!("{:?}", header))
130
+ }
131
+
132
+ fn get_file_path(&self) -> PyResult<String> {
133
+ Ok(self.parser.get_file_path().to_string_lossy().into_owned())
134
+ }
135
+ }
136
+
137
+ /// Create a new SHRParser instance.
138
+ #[pyfunction]
139
+ fn create_parser(file_path: &str, parsing_type: PySHRParsingType) -> PyResult<PySHRParser> {
140
+ PySHRParser::new(file_path, parsing_type)
141
+ }
142
+
143
+ /// A Python module implemented in Rust.
144
+ #[pymodule(name = "shr_parser")]
145
+ fn shr_parser_py(m: &Bound<PyModule>) -> PyResult<()> {
146
+ m.add_class::<PySHRParser>()?;
147
+ m.add_class::<PySHRSweep>()?;
148
+ m.add_class::<PySHRParsingType>()?;
149
+ m.add_function(wrap_pyfunction!(create_parser, m)?)?;
150
+ Ok(())
151
+ }
@@ -1,61 +0,0 @@
1
- use pyo3::prelude::*;
2
- use pyo3::wrap_pyfunction;
3
- use std::path::PathBuf;
4
- use ::shr_parser::{SHRParser, SHRParsingType};
5
-
6
- /// A wrapper around the SHRParser for Python
7
- #[pyclass]
8
- struct PySHRParser {
9
- parser: SHRParser,
10
- }
11
-
12
- #[pymethods]
13
- impl PySHRParser {
14
- #[new]
15
- fn new(file_path: String, parsing_type: i32) -> PyResult<Self> {
16
- let parsing_type = SHRParsingType::try_from(parsing_type).map_err(|e| {
17
- pyo3::exceptions::PyValueError::new_err(format!("Invalid parsing type: {}", e))
18
- })?;
19
- let parser = SHRParser::new(PathBuf::from(file_path), parsing_type).map_err(|e| {
20
- pyo3::exceptions::PyIOError::new_err(format!("Failed to parse SHR file: {:?}", e))
21
- })?;
22
- Ok(PySHRParser { parser })
23
- }
24
-
25
- fn to_csv(&self, path: String) -> PyResult<()> {
26
- self.parser.to_csv(PathBuf::from(path)).map_err(|e| {
27
- pyo3::exceptions::PyIOError::new_err(format!("Failed to write to CSV: {:?}", e))
28
- })
29
- }
30
-
31
- fn get_sweeps(&self) -> PyResult<Vec<(i32, u64, f64, f64)>> {
32
- let sweeps = self.parser.get_sweeps();
33
- Ok(sweeps
34
- .into_iter()
35
- .map(|sweep| (sweep.sweep_number, sweep.timestamp, sweep.frequency, sweep.amplitude))
36
- .collect())
37
- }
38
-
39
- fn get_file_header(&self) -> PyResult<String> {
40
- let header = self.parser.get_file_header();
41
- Ok(format!("{:?}", header))
42
- }
43
-
44
- fn get_file_path(&self) -> PyResult<String> {
45
- Ok(self.parser.get_file_path().to_string_lossy().to_string())
46
- }
47
- }
48
-
49
- /// Create a new SHRParser instance.
50
- #[pyfunction]
51
- fn create_parser(file_path: String, parsing_type: i32) -> PyResult<PySHRParser> {
52
- PySHRParser::new(file_path, parsing_type)
53
- }
54
-
55
- /// A Python module implemented in Rust.
56
- #[pymodule]
57
- fn shr_parser(module: &Bound<'_, PyModule>) -> PyResult<()> {
58
- module.add_class::<PySHRParser>()?;
59
- module.add_function(wrap_pyfunction!(create_parser, module)?)?;
60
- Ok(())
61
- }
File without changes
File without changes