siderust-js 0.1.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.
Files changed (272) hide show
  1. package/.github/workflows/ci.yml +166 -0
  2. package/.gitmodules +9 -0
  3. package/CHANGELOG.md +26 -0
  4. package/LICENSE +661 -0
  5. package/README.md +138 -0
  6. package/package.json +12 -0
  7. package/qtty-js/.github/workflows/ci.yml +151 -0
  8. package/qtty-js/.gitmodules +3 -0
  9. package/qtty-js/CHANGELOG.md +31 -0
  10. package/qtty-js/LICENSE +661 -0
  11. package/qtty-js/README.md +132 -0
  12. package/qtty-js/package.json +20 -0
  13. package/qtty-js/qtty/.github/workflows/ci.yml +155 -0
  14. package/qtty-js/qtty/CHANGELOG.md +120 -0
  15. package/qtty-js/qtty/Cargo.lock +1462 -0
  16. package/qtty-js/qtty/Cargo.toml +12 -0
  17. package/qtty-js/qtty/LICENSE +661 -0
  18. package/qtty-js/qtty/README.md +9 -0
  19. package/qtty-js/qtty/qtty/Cargo.toml +41 -0
  20. package/qtty-js/qtty/qtty/README.md +8 -0
  21. package/qtty-js/qtty/qtty/examples/angles.rs +14 -0
  22. package/qtty-js/qtty/qtty/examples/astronomy.rs +17 -0
  23. package/qtty-js/qtty/qtty/examples/dimensional_arithmetic.rs +83 -0
  24. package/qtty-js/qtty/qtty/examples/python_integration.rs +61 -0
  25. package/qtty-js/qtty/qtty/examples/quickstart.rs +15 -0
  26. package/qtty-js/qtty/qtty/examples/ratios.rs +12 -0
  27. package/qtty-js/qtty/qtty/examples/serde_with_unit.rs +234 -0
  28. package/qtty-js/qtty/qtty/examples/serialization.rs +141 -0
  29. package/qtty-js/qtty/qtty/examples/serialization_advanced.rs +155 -0
  30. package/qtty-js/qtty/qtty/src/f32.rs +108 -0
  31. package/qtty-js/qtty/qtty/src/f64.rs +30 -0
  32. package/qtty-js/qtty/qtty/src/i128.rs +111 -0
  33. package/qtty-js/qtty/qtty/src/i16.rs +111 -0
  34. package/qtty-js/qtty/qtty/src/i32.rs +111 -0
  35. package/qtty-js/qtty/qtty/src/i64.rs +111 -0
  36. package/qtty-js/qtty/qtty/src/i8.rs +111 -0
  37. package/qtty-js/qtty/qtty/src/lib.rs +238 -0
  38. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.lock +83 -0
  39. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.toml +10 -0
  40. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/src/lib.rs +7 -0
  41. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.lock +83 -0
  42. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.toml +10 -0
  43. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/src/lib.rs +7 -0
  44. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.lock +83 -0
  45. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.toml +10 -0
  46. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/src/lib.rs +5 -0
  47. package/qtty-js/qtty/qtty/tests/integration_tests.rs +529 -0
  48. package/qtty-js/qtty/qtty/tests/qtty_vec_feature_matrix.rs +58 -0
  49. package/qtty-js/qtty/qtty-core/Cargo.toml +41 -0
  50. package/qtty-js/qtty/qtty-core/README.md +8 -0
  51. package/qtty-js/qtty/qtty-core/examples/diesel_integration.rs +145 -0
  52. package/qtty-js/qtty/qtty-core/examples/quantity_db_serde.rs +215 -0
  53. package/qtty-js/qtty/qtty-core/src/dimension.rs +249 -0
  54. package/qtty-js/qtty/qtty-core/src/feature_diesel.rs +318 -0
  55. package/qtty-js/qtty/qtty-core/src/feature_pyo3.rs +27 -0
  56. package/qtty-js/qtty/qtty-core/src/feature_serde.rs +203 -0
  57. package/qtty-js/qtty/qtty-core/src/feature_tiberius.rs +28 -0
  58. package/qtty-js/qtty/qtty-core/src/lib.rs +744 -0
  59. package/qtty-js/qtty/qtty-core/src/macros.rs +93 -0
  60. package/qtty-js/qtty/qtty-core/src/quantity.rs +810 -0
  61. package/qtty-js/qtty/qtty-core/src/scalar.rs +1742 -0
  62. package/qtty-js/qtty/qtty-core/src/unit.rs +332 -0
  63. package/qtty-js/qtty/qtty-core/src/units/angular.rs +1228 -0
  64. package/qtty-js/qtty/qtty-core/src/units/area.rs +243 -0
  65. package/qtty-js/qtty/qtty-core/src/units/frequency.rs +179 -0
  66. package/qtty-js/qtty/qtty-core/src/units/length.rs +1270 -0
  67. package/qtty-js/qtty/qtty-core/src/units/mass.rs +488 -0
  68. package/qtty-js/qtty/qtty-core/src/units/mod.rs +26 -0
  69. package/qtty-js/qtty/qtty-core/src/units/power.rs +324 -0
  70. package/qtty-js/qtty/qtty-core/src/units/time.rs +667 -0
  71. package/qtty-js/qtty/qtty-core/src/units/unitless.rs +212 -0
  72. package/qtty-js/qtty/qtty-core/src/units/velocity.rs +210 -0
  73. package/qtty-js/qtty/qtty-core/src/units/volume.rs +269 -0
  74. package/qtty-js/qtty/qtty-core/tests/core.rs +628 -0
  75. package/qtty-js/qtty/qtty-core/tests/diesel.rs +461 -0
  76. package/qtty-js/qtty/qtty-core/tests/integers.rs +632 -0
  77. package/qtty-js/qtty/qtty-core/tests/no_cross_unit_ops.rs +35 -0
  78. package/qtty-js/qtty/qtty-core/tests/pyo3.rs +334 -0
  79. package/qtty-js/qtty/qtty-core/tests/quantity_f32.rs +276 -0
  80. package/qtty-js/qtty/qtty-core/tests/scalar_decimal.rs +258 -0
  81. package/qtty-js/qtty/qtty-core/tests/scalar_f32.rs +286 -0
  82. package/qtty-js/qtty/qtty-core/tests/scalar_f64_real.rs +287 -0
  83. package/qtty-js/qtty/qtty-core/tests/scalar_rational.rs +260 -0
  84. package/qtty-js/qtty/qtty-core/tests/serde.rs +256 -0
  85. package/qtty-js/qtty/qtty-core/tests/tiberius.rs +208 -0
  86. package/qtty-js/qtty/qtty-derive/Cargo.toml +23 -0
  87. package/qtty-js/qtty/qtty-derive/README.md +8 -0
  88. package/qtty-js/qtty/qtty-derive/src/lib.rs +340 -0
  89. package/qtty-js/qtty/qtty-ffi/ARCHITECTURE.md +3 -0
  90. package/qtty-js/qtty/qtty-ffi/Cargo.toml +31 -0
  91. package/qtty-js/qtty/qtty-ffi/README.md +9 -0
  92. package/qtty-js/qtty/qtty-ffi/build.rs +326 -0
  93. package/qtty-js/qtty/qtty-ffi/cbindgen.toml +105 -0
  94. package/qtty-js/qtty/qtty-ffi/include/qtty_ffi.h +1126 -0
  95. package/qtty-js/qtty/qtty-ffi/src/ffi.rs +1251 -0
  96. package/qtty-js/qtty/qtty-ffi/src/ffi_serde.rs +294 -0
  97. package/qtty-js/qtty/qtty-ffi/src/helpers.rs +310 -0
  98. package/qtty-js/qtty/qtty-ffi/src/lib.rs +229 -0
  99. package/qtty-js/qtty/qtty-ffi/src/macros.rs +121 -0
  100. package/qtty-js/qtty/qtty-ffi/src/registry.rs +274 -0
  101. package/qtty-js/qtty/qtty-ffi/src/types.rs +620 -0
  102. package/qtty-js/qtty/qtty-ffi/tests/integration_tests.rs +842 -0
  103. package/qtty-js/qtty/qtty-ffi/units.csv +156 -0
  104. package/qtty-js/qtty/qtty-ffi/units.csv.md +3 -0
  105. package/qtty-js/qtty-node/.prettierignore +6 -0
  106. package/qtty-js/qtty-node/.prettierrc.json +6 -0
  107. package/qtty-js/qtty-node/README.md +250 -0
  108. package/qtty-js/qtty-node/c8.config.json +11 -0
  109. package/qtty-js/qtty-node/eslint.config.js +31 -0
  110. package/qtty-js/qtty-node/examples/arithmetic.mjs +64 -0
  111. package/qtty-js/qtty-node/examples/astronomy.mjs +90 -0
  112. package/qtty-js/qtty-node/examples/quickstart.mjs +36 -0
  113. package/qtty-js/qtty-node/examples/serialization.mjs +125 -0
  114. package/qtty-js/qtty-node/examples/unit_factories.mjs +74 -0
  115. package/qtty-js/qtty-node/index.d.ts +219 -0
  116. package/qtty-js/qtty-node/index.js +323 -0
  117. package/qtty-js/qtty-node/lib/DerivedQuantity.js +122 -0
  118. package/qtty-js/qtty-node/lib/Quantity.js +151 -0
  119. package/qtty-js/qtty-node/lib/backend.js +25 -0
  120. package/qtty-js/qtty-node/native.cjs +306 -0
  121. package/qtty-js/qtty-node/package-lock.json +3223 -0
  122. package/qtty-js/qtty-node/package.json +70 -0
  123. package/qtty-js/qtty-node/units.d.ts +299 -0
  124. package/qtty-js/qtty-node/units.js +210 -0
  125. package/qtty-js/qtty-web/Cargo.lock +767 -0
  126. package/qtty-js/qtty-web/Cargo.toml +21 -0
  127. package/qtty-js/qtty-web/index.d.ts +140 -0
  128. package/qtty-js/qtty-web/index.js +20 -0
  129. package/qtty-js/qtty-web/lib/DerivedQuantity.js +58 -0
  130. package/qtty-js/qtty-web/lib/Quantity.js +75 -0
  131. package/qtty-js/qtty-web/lib/backend.js +80 -0
  132. package/qtty-js/qtty-web/package.json +45 -0
  133. package/qtty-js/qtty-web/src/lib.rs +111 -0
  134. package/qtty-js/scripts/ci.sh +73 -0
  135. package/scripts/ci.sh +123 -0
  136. package/siderust-core/Cargo.lock +787 -0
  137. package/siderust-core/Cargo.toml +18 -0
  138. package/siderust-core/DEDUPLICATION.md +124 -0
  139. package/siderust-core/src/body.rs +120 -0
  140. package/siderust-core/src/events.rs +184 -0
  141. package/siderust-core/src/lib.rs +20 -0
  142. package/siderust-core/src/observer.rs +55 -0
  143. package/siderust-core/src/position.rs +213 -0
  144. package/siderust-node/.prettierignore +7 -0
  145. package/siderust-node/.prettierrc.json +6 -0
  146. package/siderust-node/Cargo.lock +906 -0
  147. package/siderust-node/Cargo.toml +29 -0
  148. package/siderust-node/README.md +109 -0
  149. package/siderust-node/__test__/index.test.mjs +248 -0
  150. package/siderust-node/build.rs +5 -0
  151. package/siderust-node/c8.config.json +3 -0
  152. package/siderust-node/eslint.config.js +31 -0
  153. package/siderust-node/examples/01_basic_coordinates.mjs +24 -0
  154. package/siderust-node/examples/02_coordinate_transformations.mjs +25 -0
  155. package/siderust-node/examples/03_all_frames_conversions.mjs +26 -0
  156. package/siderust-node/examples/04_all_center_conversions.mjs +24 -0
  157. package/siderust-node/examples/05_target_tracking.mjs +22 -0
  158. package/siderust-node/examples/06_night_events.mjs +18 -0
  159. package/siderust-node/examples/07_moon_properties.mjs +21 -0
  160. package/siderust-node/examples/08_solar_system.mjs +19 -0
  161. package/siderust-node/examples/09_star_observability.mjs +22 -0
  162. package/siderust-node/examples/10_time_periods.mjs +9 -0
  163. package/siderust-node/examples/11_serialization.mjs +31 -0
  164. package/siderust-node/examples/12_runtime_ephemeris.mjs +27 -0
  165. package/siderust-node/examples/13_coordinate_operations.mjs +20 -0
  166. package/siderust-node/index.d.ts +623 -0
  167. package/siderust-node/index.js +79 -0
  168. package/siderust-node/lib/Observer.js +112 -0
  169. package/siderust-node/lib/Star.js +118 -0
  170. package/siderust-node/lib/backend.js +63 -0
  171. package/siderust-node/lib/wrappers.js +566 -0
  172. package/siderust-node/main.js +20 -0
  173. package/siderust-node/native.cjs +360 -0
  174. package/siderust-node/package-lock.json +3261 -0
  175. package/siderust-node/package.json +71 -0
  176. package/siderust-node/src/body.rs +74 -0
  177. package/siderust-node/src/coordinates.rs +372 -0
  178. package/siderust-node/src/ephemeris.rs +462 -0
  179. package/siderust-node/src/events.rs +577 -0
  180. package/siderust-node/src/lib.rs +43 -0
  181. package/siderust-node/src/observer.rs +132 -0
  182. package/siderust-node/src/phase.rs +218 -0
  183. package/siderust-node/src/position.rs +292 -0
  184. package/siderust-node/src/star.rs +200 -0
  185. package/siderust-web/Cargo.lock +855 -0
  186. package/siderust-web/Cargo.toml +34 -0
  187. package/siderust-web/README.md +100 -0
  188. package/siderust-web/__test__/index.test.mjs +118 -0
  189. package/siderust-web/examples/github-pages/README.md +31 -0
  190. package/siderust-web/examples/github-pages/index.html +135 -0
  191. package/siderust-web/index.d.ts +311 -0
  192. package/siderust-web/index.js +66 -0
  193. package/siderust-web/lib/Observer.js +103 -0
  194. package/siderust-web/lib/Star.js +116 -0
  195. package/siderust-web/lib/backend.js +400 -0
  196. package/siderust-web/lib/wrappers.js +512 -0
  197. package/siderust-web/package.json +55 -0
  198. package/siderust-web/src/body.rs +69 -0
  199. package/siderust-web/src/coordinates.rs +302 -0
  200. package/siderust-web/src/ephemeris.rs +456 -0
  201. package/siderust-web/src/events.rs +520 -0
  202. package/siderust-web/src/lib.rs +51 -0
  203. package/siderust-web/src/observer.rs +117 -0
  204. package/siderust-web/src/phase.rs +190 -0
  205. package/siderust-web/src/position.rs +291 -0
  206. package/siderust-web/src/star.rs +178 -0
  207. package/tempoch-js/.github/workflows/ci.yml +142 -0
  208. package/tempoch-js/.gitmodules +3 -0
  209. package/tempoch-js/CHANGELOG.md +25 -0
  210. package/tempoch-js/LICENSE +661 -0
  211. package/tempoch-js/README.md +126 -0
  212. package/tempoch-js/package.json +20 -0
  213. package/tempoch-js/scripts/ci.sh +73 -0
  214. package/tempoch-js/tempoch/.github/workflows/ci.yml +113 -0
  215. package/tempoch-js/tempoch/CHANGELOG.md +82 -0
  216. package/tempoch-js/tempoch/Cargo.lock +947 -0
  217. package/tempoch-js/tempoch/Cargo.toml +3 -0
  218. package/tempoch-js/tempoch/LICENSE +661 -0
  219. package/tempoch-js/tempoch/README.md +76 -0
  220. package/tempoch-js/tempoch/tempoch/Cargo.toml +27 -0
  221. package/tempoch-js/tempoch/tempoch/examples/periods.rs +45 -0
  222. package/tempoch-js/tempoch/tempoch/examples/quickstart.rs +13 -0
  223. package/tempoch-js/tempoch/tempoch/src/lib.rs +49 -0
  224. package/tempoch-js/tempoch/tempoch/tests/integration.rs +57 -0
  225. package/tempoch-js/tempoch/tempoch-core/Cargo.toml +24 -0
  226. package/tempoch-js/tempoch/tempoch-core/src/delta_t.rs +345 -0
  227. package/tempoch-js/tempoch/tempoch-core/src/instant.rs +811 -0
  228. package/tempoch-js/tempoch/tempoch-core/src/julian_date_ext.rs +142 -0
  229. package/tempoch-js/tempoch/tempoch-core/src/lib.rs +81 -0
  230. package/tempoch-js/tempoch/tempoch-core/src/period.rs +1168 -0
  231. package/tempoch-js/tempoch/tempoch-core/src/scales.rs +779 -0
  232. package/tempoch-js/tempoch/tempoch-ffi/Cargo.lock +889 -0
  233. package/tempoch-js/tempoch/tempoch-ffi/Cargo.toml +26 -0
  234. package/tempoch-js/tempoch/tempoch-ffi/build.rs +24 -0
  235. package/tempoch-js/tempoch/tempoch-ffi/cbindgen.toml +30 -0
  236. package/tempoch-js/tempoch/tempoch-ffi/src/error.rs +19 -0
  237. package/tempoch-js/tempoch/tempoch-ffi/src/lib.rs +82 -0
  238. package/tempoch-js/tempoch/tempoch-ffi/src/period.rs +101 -0
  239. package/tempoch-js/tempoch/tempoch-ffi/src/time.rs +711 -0
  240. package/tempoch-js/tempoch/tempoch-ffi/tests/ffi.rs +265 -0
  241. package/tempoch-js/tempoch-node/.prettierignore +6 -0
  242. package/tempoch-js/tempoch-node/.prettierrc.json +6 -0
  243. package/tempoch-js/tempoch-node/Cargo.lock +496 -0
  244. package/tempoch-js/tempoch-node/Cargo.toml +29 -0
  245. package/tempoch-js/tempoch-node/README.md +265 -0
  246. package/tempoch-js/tempoch-node/__test__/index.test.mjs +598 -0
  247. package/tempoch-js/tempoch-node/build.rs +5 -0
  248. package/tempoch-js/tempoch-node/c8.config.json +3 -0
  249. package/tempoch-js/tempoch-node/eslint.config.js +31 -0
  250. package/tempoch-js/tempoch-node/examples/periods.mjs +79 -0
  251. package/tempoch-js/tempoch-node/examples/quickstart.mjs +71 -0
  252. package/tempoch-js/tempoch-node/examples/timescales.mjs +92 -0
  253. package/tempoch-js/tempoch-node/index.d.ts +280 -0
  254. package/tempoch-js/tempoch-node/index.js +32 -0
  255. package/tempoch-js/tempoch-node/lib/JulianDate.js +176 -0
  256. package/tempoch-js/tempoch-node/lib/ModifiedJulianDate.js +156 -0
  257. package/tempoch-js/tempoch-node/lib/Period.js +133 -0
  258. package/tempoch-js/tempoch-node/lib/backend.js +38 -0
  259. package/tempoch-js/tempoch-node/lib/qttyCompat.js +92 -0
  260. package/tempoch-js/tempoch-node/native.cjs +317 -0
  261. package/tempoch-js/tempoch-node/package-lock.json +3223 -0
  262. package/tempoch-js/tempoch-node/package.json +56 -0
  263. package/tempoch-js/tempoch-node/src/lib.rs +573 -0
  264. package/tempoch-js/tempoch-web/Cargo.toml +23 -0
  265. package/tempoch-js/tempoch-web/index.d.ts +95 -0
  266. package/tempoch-js/tempoch-web/index.js +27 -0
  267. package/tempoch-js/tempoch-web/lib/JulianDate.js +170 -0
  268. package/tempoch-js/tempoch-web/lib/ModifiedJulianDate.js +145 -0
  269. package/tempoch-js/tempoch-web/lib/Period.js +121 -0
  270. package/tempoch-js/tempoch-web/lib/backend.js +118 -0
  271. package/tempoch-js/tempoch-web/package.json +46 -0
  272. package/tempoch-js/tempoch-web/src/lib.rs +184 -0
@@ -0,0 +1,340 @@
1
+ //! Derive macro implementation used by `qtty-core`.
2
+ //!
3
+ //! `qtty-derive` is an implementation detail of this workspace. The `Unit` derive expands in terms of `crate::Unit`
4
+ //! and `crate::Quantity`, so it is intended to be used by `qtty-core` (or by crates that expose an identical
5
+ //! crate-root API).
6
+ //!
7
+ //! Most users should depend on `qtty` instead and use the predefined units.
8
+ //!
9
+ //! # Generated impls
10
+ //!
11
+ //! For a unit marker type `MyUnit`, the derive implements:
12
+ //!
13
+ //! - `crate::Unit for MyUnit`
14
+ //! - `core::fmt::Display for crate::Quantity<MyUnit>` (formats as `<value> <symbol>`)
15
+ //!
16
+ //! # Attributes
17
+ //!
18
+ //! The derive reads a required `#[unit(...)]` attribute:
19
+ //!
20
+ //! - `symbol = "m"`: displayed unit symbol
21
+ //! - `dimension = SomeDim`: dimension marker type
22
+ //! - `ratio = 1000.0`: conversion ratio to the canonical unit of the dimension
23
+
24
+ #![deny(missing_docs)]
25
+ #![forbid(unsafe_code)]
26
+
27
+ use proc_macro::TokenStream;
28
+ use proc_macro2::TokenStream as TokenStream2;
29
+ use quote::quote;
30
+ use syn::{
31
+ parse::{Parse, ParseStream},
32
+ parse_macro_input, Attribute, DeriveInput, Expr, Ident, LitStr, Token,
33
+ };
34
+
35
+ /// Derive `crate::Unit` and a `Display` impl for `crate::Quantity<ThisUnit>`.
36
+ ///
37
+ /// The derive must be paired with a `#[unit(...)]` attribute providing `symbol`, `dimension`, and `ratio`.
38
+ ///
39
+ /// This macro is intended for use by `qtty-core`.
40
+ #[proc_macro_derive(Unit, attributes(unit))]
41
+ pub fn derive_unit(input: TokenStream) -> TokenStream {
42
+ let input = parse_macro_input!(input as DeriveInput);
43
+
44
+ match derive_unit_impl(input) {
45
+ Ok(tokens) => tokens.into(),
46
+ Err(err) => err.to_compile_error().into(),
47
+ }
48
+ }
49
+
50
+ fn derive_unit_impl(input: DeriveInput) -> syn::Result<TokenStream2> {
51
+ let name = &input.ident;
52
+
53
+ // Parse the #[unit(...)] attribute
54
+ let unit_attr = parse_unit_attribute(&input.attrs)?;
55
+
56
+ let symbol = &unit_attr.symbol;
57
+ let dimension = &unit_attr.dimension;
58
+ let ratio = &unit_attr.ratio;
59
+
60
+ let expanded = quote! {
61
+ impl crate::Unit for #name {
62
+ const RATIO: f64 = #ratio;
63
+ type Dim = #dimension;
64
+ const SYMBOL: &'static str = #symbol;
65
+ }
66
+
67
+ impl<S: crate::scalar::Scalar + ::core::fmt::Display> ::core::fmt::Display for crate::Quantity<#name, S> {
68
+ fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
69
+ ::core::fmt::Display::fmt(&self.value(), f)?;
70
+ write!(f, " {}", <#name as crate::Unit>::SYMBOL)
71
+ }
72
+ }
73
+
74
+ impl<S: crate::scalar::Scalar + ::core::fmt::LowerExp> ::core::fmt::LowerExp for crate::Quantity<#name, S> {
75
+ fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
76
+ ::core::fmt::LowerExp::fmt(&self.value(), f)?;
77
+ write!(f, " {}", <#name as crate::Unit>::SYMBOL)
78
+ }
79
+ }
80
+
81
+ impl<S: crate::scalar::Scalar + ::core::fmt::UpperExp> ::core::fmt::UpperExp for crate::Quantity<#name, S> {
82
+ fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
83
+ ::core::fmt::UpperExp::fmt(&self.value(), f)?;
84
+ write!(f, " {}", <#name as crate::Unit>::SYMBOL)
85
+ }
86
+ }
87
+ };
88
+
89
+ Ok(expanded)
90
+ }
91
+
92
+ /// Parsed contents of the `#[unit(...)]` attribute.
93
+ struct UnitAttribute {
94
+ symbol: LitStr,
95
+ dimension: Expr,
96
+ ratio: Expr,
97
+ // Future extensions:
98
+ // long_name: Option<LitStr>,
99
+ // plural: Option<LitStr>,
100
+ // system: Option<LitStr>,
101
+ // base_unit: Option<bool>,
102
+ // aliases: Option<Vec<LitStr>>,
103
+ }
104
+
105
+ impl Parse for UnitAttribute {
106
+ fn parse(input: ParseStream) -> syn::Result<Self> {
107
+ let mut symbol: Option<LitStr> = None;
108
+ let mut dimension: Option<Expr> = None;
109
+ let mut ratio: Option<Expr> = None;
110
+
111
+ while !input.is_empty() {
112
+ let ident: Ident = input.parse()?;
113
+ input.parse::<Token![=]>()?;
114
+
115
+ match ident.to_string().as_str() {
116
+ "symbol" => {
117
+ symbol = Some(input.parse()?);
118
+ }
119
+ "dimension" => {
120
+ dimension = Some(input.parse()?);
121
+ }
122
+ "ratio" => {
123
+ ratio = Some(input.parse()?);
124
+ }
125
+ // Future extensions would be handled here:
126
+ // "long_name" => { ... }
127
+ // "plural" => { ... }
128
+ // "system" => { ... }
129
+ // "base_unit" => { ... }
130
+ // "aliases" => { ... }
131
+ other => {
132
+ return Err(syn::Error::new(
133
+ ident.span(),
134
+ format!("unknown attribute `{}`", other),
135
+ ));
136
+ }
137
+ }
138
+
139
+ // Consume trailing comma if present
140
+ if input.peek(Token![,]) {
141
+ input.parse::<Token![,]>()?;
142
+ }
143
+ }
144
+
145
+ let symbol = symbol
146
+ .ok_or_else(|| syn::Error::new(input.span(), "missing required attribute `symbol`"))?;
147
+ let dimension = dimension.ok_or_else(|| {
148
+ syn::Error::new(input.span(), "missing required attribute `dimension`")
149
+ })?;
150
+ let ratio = ratio
151
+ .ok_or_else(|| syn::Error::new(input.span(), "missing required attribute `ratio`"))?;
152
+
153
+ Ok(UnitAttribute {
154
+ symbol,
155
+ dimension,
156
+ ratio,
157
+ })
158
+ }
159
+ }
160
+
161
+ fn parse_unit_attribute(attrs: &[Attribute]) -> syn::Result<UnitAttribute> {
162
+ for attr in attrs {
163
+ if attr.path().is_ident("unit") {
164
+ return attr.parse_args::<UnitAttribute>();
165
+ }
166
+ }
167
+
168
+ Err(syn::Error::new(
169
+ proc_macro2::Span::call_site(),
170
+ "missing #[unit(...)] attribute",
171
+ ))
172
+ }
173
+
174
+ #[cfg(test)]
175
+ mod tests {
176
+ use super::*;
177
+ use quote::quote;
178
+ use syn::parse_quote;
179
+
180
+ #[test]
181
+ fn test_parse_unit_attribute_complete() {
182
+ let input: DeriveInput = parse_quote! {
183
+ #[unit(symbol = "m", dimension = Length, ratio = 1.0)]
184
+ pub enum Meter {}
185
+ };
186
+
187
+ let attr = parse_unit_attribute(&input.attrs).unwrap();
188
+ assert_eq!(attr.symbol.value(), "m");
189
+ }
190
+
191
+ #[test]
192
+ fn test_parse_unit_attribute_missing() {
193
+ let input: DeriveInput = parse_quote! {
194
+ pub enum Meter {}
195
+ };
196
+
197
+ let result = parse_unit_attribute(&input.attrs);
198
+ assert!(result.is_err());
199
+ let err = result.err().unwrap();
200
+ let err_msg = err.to_string();
201
+ assert!(err_msg.contains("missing #[unit(...)] attribute"));
202
+ }
203
+
204
+ #[test]
205
+ fn test_parse_unit_attribute_missing_symbol() {
206
+ let input: DeriveInput = parse_quote! {
207
+ #[unit(dimension = Length, ratio = 1.0)]
208
+ pub enum Meter {}
209
+ };
210
+
211
+ let result = parse_unit_attribute(&input.attrs);
212
+ assert!(result.is_err());
213
+ let err = result.err().unwrap();
214
+ let err_msg = err.to_string();
215
+ assert!(err_msg.contains("missing required attribute `symbol`"));
216
+ }
217
+
218
+ #[test]
219
+ fn test_parse_unit_attribute_missing_dimension() {
220
+ let input: DeriveInput = parse_quote! {
221
+ #[unit(symbol = "m", ratio = 1.0)]
222
+ pub enum Meter {}
223
+ };
224
+
225
+ let result = parse_unit_attribute(&input.attrs);
226
+ assert!(result.is_err());
227
+ let err = result.err().unwrap();
228
+ let err_msg = err.to_string();
229
+ assert!(err_msg.contains("missing required attribute `dimension`"));
230
+ }
231
+
232
+ #[test]
233
+ fn test_parse_unit_attribute_missing_ratio() {
234
+ let input: DeriveInput = parse_quote! {
235
+ #[unit(symbol = "m", dimension = Length)]
236
+ pub enum Meter {}
237
+ };
238
+
239
+ let result = parse_unit_attribute(&input.attrs);
240
+ assert!(result.is_err());
241
+ let err = result.err().unwrap();
242
+ let err_msg = err.to_string();
243
+ assert!(err_msg.contains("missing required attribute `ratio`"));
244
+ }
245
+
246
+ #[test]
247
+ fn test_parse_unit_attribute_unknown_field() {
248
+ let input: DeriveInput = parse_quote! {
249
+ #[unit(symbol = "m", dimension = Length, ratio = 1.0, unknown = "value")]
250
+ pub enum Meter {}
251
+ };
252
+
253
+ let result = parse_unit_attribute(&input.attrs);
254
+ assert!(result.is_err());
255
+ let err = result.err().unwrap();
256
+ let err_msg = err.to_string();
257
+ assert!(err_msg.contains("unknown attribute"));
258
+ }
259
+
260
+ #[test]
261
+ fn test_derive_unit_impl_basic() {
262
+ let input: DeriveInput = parse_quote! {
263
+ #[unit(symbol = "m", dimension = Length, ratio = 1.0)]
264
+ pub enum Meter {}
265
+ };
266
+
267
+ let result = derive_unit_impl(input);
268
+ assert!(result.is_ok());
269
+ let tokens = result.unwrap();
270
+ let code = tokens.to_string();
271
+ assert!(code.contains("impl crate :: Unit for Meter"));
272
+ assert!(code.contains("const RATIO : f64 = 1.0"));
273
+ assert!(code.contains("const SYMBOL : & 'static str = \"m\""));
274
+ assert!(code.contains("type Dim = Length"));
275
+ }
276
+
277
+ #[test]
278
+ fn test_derive_unit_impl_with_expression_ratio() {
279
+ let input: DeriveInput = parse_quote! {
280
+ #[unit(symbol = "km", dimension = Length, ratio = 1000.0)]
281
+ pub enum Kilometer {}
282
+ };
283
+
284
+ let result = derive_unit_impl(input);
285
+ assert!(result.is_ok());
286
+ let tokens = result.unwrap();
287
+ let code = tokens.to_string();
288
+ assert!(code.contains("const RATIO : f64 = 1000.0"));
289
+ }
290
+
291
+ #[test]
292
+ fn test_unit_attribute_parse_with_trailing_comma() {
293
+ let tokens = quote! {
294
+ symbol = "m", dimension = Length, ratio = 1.0,
295
+ };
296
+ let attr: UnitAttribute = syn::parse2(tokens).unwrap();
297
+ assert_eq!(attr.symbol.value(), "m");
298
+ }
299
+
300
+ #[test]
301
+ fn test_unit_attribute_parse_no_trailing_comma() {
302
+ let tokens = quote! {
303
+ symbol = "m", dimension = Length, ratio = 1.0
304
+ };
305
+ let attr: UnitAttribute = syn::parse2(tokens).unwrap();
306
+ assert_eq!(attr.symbol.value(), "m");
307
+ }
308
+
309
+ #[test]
310
+ fn test_unit_attribute_parse_duplicate_symbol() {
311
+ // Parser accepts duplicates - last one wins
312
+ let tokens = quote! {
313
+ symbol = "m", symbol = "km", dimension = Length, ratio = 1.0
314
+ };
315
+ let attr: UnitAttribute = syn::parse2(tokens).unwrap();
316
+ assert_eq!(attr.symbol.value(), "km");
317
+ }
318
+
319
+ #[test]
320
+ fn test_parse_empty_attribute() {
321
+ let tokens = quote! {};
322
+ let result: syn::Result<UnitAttribute> = syn::parse2(tokens);
323
+ assert!(result.is_err());
324
+ }
325
+
326
+ #[test]
327
+ fn test_derive_unit_impl_error_path() {
328
+ // Test error handling in derive_unit_impl
329
+ let input: DeriveInput = parse_quote! {
330
+ pub enum Meter {}
331
+ };
332
+ let result = derive_unit_impl(input);
333
+ assert!(result.is_err());
334
+ // The error should contain information about missing attribute
335
+ let err = result.err().unwrap();
336
+ let err_tokens = err.to_compile_error();
337
+ let code = err_tokens.to_string();
338
+ assert!(code.contains("compile_error"));
339
+ }
340
+ }
@@ -0,0 +1,3 @@
1
+ # qtty-ffi architecture
2
+
3
+ Canonical architecture notes now live in [`../../doc/architecture/qtty-ffi.md`](../../doc/architecture/qtty-ffi.md).
@@ -0,0 +1,31 @@
1
+ [package]
2
+ name = "qtty-ffi"
3
+ version.workspace = true
4
+ edition.workspace = true
5
+ authors.workspace = true
6
+ license.workspace = true
7
+ repository.workspace = true
8
+ keywords = ["ffi", "quantities", "c", "interop", "science"]
9
+ categories = ["science", "development-tools::ffi", "api-bindings"]
10
+ description = "py & C-compatible FFI bindings for qtty physical quantities and unit conversions"
11
+ readme = "README.md"
12
+
13
+ [lib]
14
+ crate-type = ["cdylib", "staticlib", "rlib"]
15
+ [dependencies]
16
+ qtty = { version = "0.4.0", path = "../qtty" }
17
+ serde = { version = "1.0", features = ["derive"] }
18
+ serde_json = { version = "1.0" }
19
+ pyo3 = { version = "0.28.2", optional = true }
20
+
21
+ [features]
22
+ pyo3 = ["dep:pyo3"]
23
+ qtty_serde = ["qtty/serde"]
24
+
25
+ [build-dependencies]
26
+ cbindgen = "0.29.2"
27
+ syn = { version = "2.0", features = ["full"] }
28
+ quote = "1.0"
29
+
30
+ [dev-dependencies]
31
+ approx = "0.5"
@@ -0,0 +1,9 @@
1
+ # qtty-ffi crate
2
+
3
+ Centralized documentation lives under the repository `doc/` tree.
4
+
5
+ - Architecture: [`../../doc/architecture/qtty-ffi.md`](../../doc/architecture/qtty-ffi.md)
6
+ - Unit registry: [`../../doc/developers/unit-registry.md`](../../doc/developers/unit-registry.md)
7
+ - Repository layout: [`../../doc/architecture/repository-layout.md`](../../doc/architecture/repository-layout.md)
8
+
9
+ This README remains as the crate metadata entrypoint.