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,326 @@
1
+ use std::env;
2
+ use std::fs;
3
+ use std::path::PathBuf;
4
+
5
+ fn main() {
6
+ let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
7
+ let out_dir = env::var("OUT_DIR").unwrap();
8
+
9
+ // Re-run if units.csv changes
10
+ println!("cargo:rerun-if-changed=units.csv");
11
+
12
+ // Parse units from CSV
13
+ let units = parse_units_csv(&crate_dir);
14
+
15
+ // Generate code files
16
+ generate_unit_enum(&units, &out_dir);
17
+ generate_unit_names(&units, &out_dir);
18
+ generate_unit_names_cstr(&units, &out_dir);
19
+ generate_unit_symbols(&units, &out_dir);
20
+ generate_from_u32(&units, &out_dir);
21
+ generate_registry(&units, &out_dir);
22
+ generate_unit_conversions(&units, &out_dir);
23
+
24
+ eprintln!(
25
+ "cargo:warning=Generated FFI bindings for {} units from units.csv",
26
+ units.len()
27
+ );
28
+
29
+ // Generate C header (existing functionality)
30
+ generate_c_header(&crate_dir);
31
+ }
32
+
33
+ #[derive(Debug, Clone)]
34
+ struct UnitDef {
35
+ name: String,
36
+ symbol: String,
37
+ dimension: String,
38
+ discriminant: u32,
39
+ ratio: String,
40
+ /// Optional Rust type path for auto-generating From/TryFrom impls.
41
+ /// When present, generates `impl_unit_ffi!(rust_type, UnitId::name)`.
42
+ rust_type: Option<String>,
43
+ }
44
+
45
+ fn parse_units_csv(crate_dir: &str) -> Vec<UnitDef> {
46
+ let csv_path = PathBuf::from(crate_dir).join("units.csv");
47
+ let content = fs::read_to_string(&csv_path).expect("Failed to read units.csv");
48
+
49
+ let mut units = Vec::new();
50
+
51
+ for line in content.lines() {
52
+ let line = line.trim();
53
+
54
+ // Skip comments and empty lines
55
+ if line.is_empty() || line.starts_with('#') {
56
+ continue;
57
+ }
58
+
59
+ let parts: Vec<&str> = line.splitn(6, ',').collect();
60
+ if parts.len() < 5 {
61
+ eprintln!("cargo:warning=Skipping invalid line: {}", line);
62
+ continue;
63
+ }
64
+
65
+ // Optional 6th field: Rust type path for From/TryFrom generation
66
+ let rust_type = if parts.len() >= 6 {
67
+ let rt = parts[5].trim();
68
+ if rt.is_empty() {
69
+ None
70
+ } else {
71
+ Some(rt.to_string())
72
+ }
73
+ } else {
74
+ None
75
+ };
76
+
77
+ units.push(UnitDef {
78
+ discriminant: parts[0]
79
+ .parse()
80
+ .unwrap_or_else(|_| panic!("Invalid discriminant: {}", parts[0])),
81
+ dimension: parts[1].to_string(),
82
+ name: parts[2].to_string(),
83
+ symbol: parts[3].to_string(),
84
+ ratio: parts[4].to_string(),
85
+ rust_type,
86
+ });
87
+ }
88
+
89
+ units
90
+ }
91
+
92
+ fn generate_unit_enum(units: &[UnitDef], out_dir: &str) {
93
+ let mut code = String::from("// Auto-generated from units.csv\n");
94
+ code.push_str("/// Unit identifier for FFI.\n");
95
+ code.push_str("///\n");
96
+ code.push_str("/// Each variant corresponds to a specific unit supported by the FFI layer.\n");
97
+ code.push_str(
98
+ "/// All discriminant values are explicitly assigned and are part of the ABI contract.\n",
99
+ );
100
+ code.push_str("#[repr(u32)]\n");
101
+ code.push_str("#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\n");
102
+ code.push_str(
103
+ "#[cfg_attr(feature = \"pyo3\", pyo3::pyclass(eq, eq_int, from_py_object, module = \"qtty\"))]\n",
104
+ );
105
+ code.push_str("pub enum UnitId {\n");
106
+
107
+ for unit in units {
108
+ code.push_str(&format!(" /// {} ({})\n", unit.name, unit.symbol));
109
+ code.push_str(&format!(" {} = {},\n", unit.name, unit.discriminant));
110
+ }
111
+
112
+ code.push_str("}\n\n");
113
+
114
+ // Add pickle support methods when pyo3 feature is enabled
115
+ code.push_str("#[cfg(feature = \"pyo3\")]\n");
116
+ code.push_str("#[pyo3::pymethods]\n");
117
+ code.push_str("impl UnitId {\n");
118
+ code.push_str(" #[new]\n");
119
+ code.push_str(" fn __new__(value: u32) -> pyo3::PyResult<Self> {\n");
120
+ code.push_str(" Self::from_u32(value).ok_or_else(|| {\n");
121
+ code.push_str(" pyo3::exceptions::PyValueError::new_err(format!(\"Invalid UnitId: {}\", value))\n");
122
+ code.push_str(" })\n");
123
+ code.push_str(" }\n");
124
+ code.push_str(" \n");
125
+ code.push_str(" fn __getnewargs__(&self) -> (u32,) {\n");
126
+ code.push_str(" (*self as u32,)\n");
127
+ code.push_str(" }\n");
128
+ code.push('\n');
129
+ code.push_str(" fn __hash__(&self) -> u64 {\n");
130
+ code.push_str(" *self as u64\n");
131
+ code.push_str(" }\n");
132
+ code.push('\n');
133
+ code.push_str(" fn __repr__(&self) -> String {\n");
134
+ code.push_str(" format!(\"Unit.{}\", self.name())\n");
135
+ code.push_str(" }\n");
136
+ code.push('\n');
137
+ // scalar * Unit → Quantity (e.g. 9.58 * Unit.Second)
138
+ code.push_str(" /// Multiply a scalar by a unit to create a Quantity.\n");
139
+ code.push_str(" ///\n");
140
+ code.push_str(" /// Example: `9.58 * Unit.Second` → `Quantity(9.58, Unit.Second)`\n");
141
+ code.push_str(" fn __mul__<'py>(&self, py: pyo3::Python<'py>, scalar: f64) -> pyo3::PyResult<pyo3::Bound<'py, pyo3::PyAny>> {\n");
142
+ code.push_str(" use pyo3::types::PyAnyMethods;\n");
143
+ code.push_str(" let qtty = py.import(\"qtty\")?;\n");
144
+ code.push_str(" let cls = qtty.getattr(\"Quantity\")?;\n");
145
+ code.push_str(" cls.call1((scalar, *self))\n");
146
+ code.push_str(" }\n");
147
+ code.push('\n');
148
+ code.push_str(
149
+ " /// Right multiplication: `Unit.Second * 9.58` → `Quantity(9.58, Unit.Second)`\n",
150
+ );
151
+ code.push_str(" fn __rmul__<'py>(&self, py: pyo3::Python<'py>, scalar: f64) -> pyo3::PyResult<pyo3::Bound<'py, pyo3::PyAny>> {\n");
152
+ code.push_str(" self.__mul__(py, scalar)\n");
153
+ code.push_str(" }\n");
154
+ code.push_str("}\n");
155
+
156
+ let dest_path = PathBuf::from(out_dir).join("unit_id_enum.rs");
157
+ fs::write(&dest_path, code).expect("Failed to write unit_id_enum.rs");
158
+ }
159
+
160
+ fn generate_unit_names(units: &[UnitDef], out_dir: &str) {
161
+ let mut code = String::from("// Auto-generated from units.csv\n");
162
+ code.push_str("match self {\n");
163
+
164
+ for unit in units {
165
+ code.push_str(&format!(
166
+ " UnitId::{} => \"{}\",\n",
167
+ unit.name, unit.name
168
+ ));
169
+ }
170
+
171
+ code.push_str("}\n");
172
+
173
+ let dest_path = PathBuf::from(out_dir).join("unit_names.rs");
174
+ fs::write(&dest_path, code).expect("Failed to write unit_names.rs");
175
+ }
176
+
177
+ fn generate_unit_names_cstr(units: &[UnitDef], out_dir: &str) {
178
+ let mut code = String::from("// Auto-generated from units.csv\n");
179
+ code.push_str("match self {\n");
180
+
181
+ for unit in units {
182
+ code.push_str(&format!(
183
+ " UnitId::{} => c\"{}\".as_ptr(),\n",
184
+ unit.name, unit.name
185
+ ));
186
+ }
187
+
188
+ code.push_str("}\n");
189
+
190
+ let dest_path = PathBuf::from(out_dir).join("unit_names_cstr.rs");
191
+ fs::write(&dest_path, code).expect("Failed to write unit_names_cstr.rs");
192
+ }
193
+
194
+ fn generate_unit_symbols(units: &[UnitDef], out_dir: &str) {
195
+ let mut code = String::from("// Auto-generated from units.csv\n");
196
+ code.push_str("match self {\n");
197
+
198
+ for unit in units {
199
+ code.push_str(&format!(
200
+ " UnitId::{} => \"{}\",\n",
201
+ unit.name, unit.symbol
202
+ ));
203
+ }
204
+
205
+ code.push_str("}\n");
206
+
207
+ let dest_path = PathBuf::from(out_dir).join("unit_symbols.rs");
208
+ fs::write(&dest_path, code).expect("Failed to write unit_symbols.rs");
209
+ }
210
+
211
+ fn generate_from_u32(units: &[UnitDef], out_dir: &str) {
212
+ let mut code = String::from("// Auto-generated from units.csv\n");
213
+ code.push_str("match value {\n");
214
+
215
+ for unit in units {
216
+ code.push_str(&format!(
217
+ " {} => Some(UnitId::{}),\n",
218
+ unit.discriminant, unit.name
219
+ ));
220
+ }
221
+
222
+ code.push_str(" _ => None,\n}\n");
223
+
224
+ let dest_path = PathBuf::from(out_dir).join("unit_from_u32.rs");
225
+ fs::write(&dest_path, code).expect("Failed to write unit_from_u32.rs");
226
+ }
227
+
228
+ fn generate_registry(units: &[UnitDef], out_dir: &str) {
229
+ let mut code = String::from("// Auto-generated from units.csv\n");
230
+ code.push_str("match id {\n");
231
+
232
+ for unit in units {
233
+ code.push_str(&format!(" UnitId::{} => Some(UnitMeta {{\n", unit.name));
234
+ code.push_str(&format!(" dim: DimensionId::{},\n", unit.dimension));
235
+ code.push_str(&format!(" scale_to_canonical: {},\n", unit.ratio));
236
+ code.push_str(&format!(" name: \"{}\",\n", unit.name));
237
+ code.push_str(" }),\n");
238
+ }
239
+
240
+ code.push_str("}\n");
241
+
242
+ let dest_path = PathBuf::from(out_dir).join("unit_registry.rs");
243
+ fs::write(&dest_path, code).expect("Failed to write unit_registry.rs");
244
+ }
245
+
246
+ fn generate_unit_conversions(units: &[UnitDef], out_dir: &str) {
247
+ let mut code = String::from("// Auto-generated From/TryFrom impls from units.csv\n");
248
+ code.push_str("// Each `impl_unit_ffi!` invocation generates:\n");
249
+ code.push_str("// impl From<RustType> for QttyQuantity\n");
250
+ code.push_str("// impl TryFrom<QttyQuantity> for RustType\n\n");
251
+
252
+ let mut count = 0;
253
+ for unit in units {
254
+ if let Some(ref rust_type) = unit.rust_type {
255
+ code.push_str(&format!(
256
+ "crate::impl_unit_ffi!({}, crate::UnitId::{});\n",
257
+ rust_type, unit.name
258
+ ));
259
+ count += 1;
260
+ }
261
+ }
262
+
263
+ eprintln!(
264
+ "cargo:warning=Generated From/TryFrom impls for {} of {} units",
265
+ count,
266
+ units.len()
267
+ );
268
+
269
+ let dest_path = PathBuf::from(out_dir).join("unit_conversions.rs");
270
+ fs::write(&dest_path, code).expect("Failed to write unit_conversions.rs");
271
+ }
272
+
273
+ fn generate_c_header(crate_dir: &str) {
274
+ if env::var("DOCS_RS").is_ok() {
275
+ return;
276
+ }
277
+
278
+ // Skip header regeneration on stable toolchain: cbindgen needs nightly
279
+ // (-Zunpretty=expanded) to see macro-generated items (e.g. UnitId enum).
280
+ // The header is maintained manually for stable builds.
281
+ let rustc = env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
282
+ let is_nightly = std::process::Command::new(&rustc)
283
+ .arg("--version")
284
+ .output()
285
+ .map(|o| String::from_utf8_lossy(&o.stdout).contains("nightly"))
286
+ .unwrap_or(false);
287
+
288
+ if !is_nightly {
289
+ eprintln!("cargo:warning=Skipping cbindgen header regeneration (stable toolchain); header maintained manually.");
290
+ println!("cargo:rerun-if-changed=src/");
291
+ println!("cargo:rerun-if-changed=cbindgen.toml");
292
+ return;
293
+ }
294
+
295
+ let out_dir = PathBuf::from(crate_dir).join("include");
296
+
297
+ if let Err(e) = std::fs::create_dir_all(&out_dir) {
298
+ eprintln!("cargo:warning=Failed to create include directory: {}", e);
299
+ return;
300
+ }
301
+
302
+ let config_path = PathBuf::from(crate_dir).join("cbindgen.toml");
303
+ let config = match cbindgen::Config::from_file(&config_path) {
304
+ Ok(c) => c,
305
+ Err(e) => {
306
+ eprintln!("cargo:warning=Failed to read cbindgen.toml: {}", e);
307
+ return;
308
+ }
309
+ };
310
+
311
+ let header_path = out_dir.join("qtty_ffi.h");
312
+ match cbindgen::Builder::new()
313
+ .with_crate(crate_dir)
314
+ .with_config(config)
315
+ .generate()
316
+ {
317
+ Ok(bindings) => {
318
+ bindings.write_to_file(&header_path);
319
+ println!("cargo:rerun-if-changed=src/");
320
+ println!("cargo:rerun-if-changed=cbindgen.toml");
321
+ }
322
+ Err(e) => {
323
+ eprintln!("cargo:warning=Failed to generate C header: {}", e);
324
+ }
325
+ }
326
+ }
@@ -0,0 +1,105 @@
1
+ # cbindgen configuration for qtty-ffi
2
+ # See https://github.com/mozilla/cbindgen/blob/master/docs.md
3
+
4
+ # Language configuration
5
+ language = "C"
6
+
7
+ # Include doc comments in header
8
+ documentation = true
9
+ documentation_style = "c"
10
+
11
+ # Header configuration
12
+ header = """
13
+ /**
14
+ * @file qtty_ffi.h
15
+ * @brief C-compatible FFI bindings for qtty physical quantities and unit conversions.
16
+ *
17
+ * This header provides the C API for the qtty-ffi library, enabling C/C++ code
18
+ * to construct and convert physical quantities using qtty's conversion logic.
19
+ *
20
+ * # Example Usage
21
+ *
22
+ * @code{.c}
23
+ * #include "qtty_ffi.h"
24
+ * #include <stdio.h>
25
+ *
26
+ * int main() {
27
+ * qtty_quantity_t meters, kilometers;
28
+ *
29
+ * // Create a quantity: 1000 meters
30
+ * int32_t status = qtty_quantity_make(1000.0, UNIT_ID_METER, &meters);
31
+ * if (status != QTTY_OK) {
32
+ * fprintf(stderr, "Failed to create quantity\\n");
33
+ * return 1;
34
+ * }
35
+ *
36
+ * // Convert to kilometers
37
+ * status = qtty_quantity_convert(meters, UNIT_ID_KILOMETER, &kilometers);
38
+ * if (status == QTTY_OK) {
39
+ * printf("1000 meters = %.2f kilometers\\n", kilometers.value);
40
+ * }
41
+ *
42
+ * return 0;
43
+ * }
44
+ * @endcode
45
+ *
46
+ * # Thread Safety
47
+ *
48
+ * All functions are thread-safe. The library contains no global mutable state.
49
+ *
50
+ * # ABI Stability
51
+ *
52
+ * Enum discriminant values and struct layouts are part of the ABI contract
53
+ * and will not change in backward-compatible releases.
54
+ *
55
+ * @version 1.0
56
+ */
57
+ """
58
+
59
+ trailer = """
60
+ /* End of qtty_ffi.h */
61
+ """
62
+
63
+ # Include guards
64
+ include_guard = "QTTY_FFI_H"
65
+
66
+ # C++ compatibility
67
+ cpp_compat = true
68
+
69
+ # Use stdint types
70
+ usize_is_size_t = true
71
+
72
+ [defines]
73
+
74
+ [export]
75
+ include = [
76
+ "UnitId", "DimensionId", "QttyQuantity", "QttyDerivedQuantity",
77
+ "QTTY_OK", "QTTY_ERR_UNKNOWN_UNIT", "QTTY_ERR_INCOMPATIBLE_DIM",
78
+ "QTTY_ERR_NULL_OUT", "QTTY_ERR_INVALID_VALUE", "QTTY_ERR_BUFFER_TOO_SMALL",
79
+ "QTTY_FMT_DEFAULT", "QTTY_FMT_LOWER_EXP", "QTTY_FMT_UPPER_EXP",
80
+ ]
81
+ exclude = ["UnitMeta"]
82
+
83
+ [export.rename]
84
+ "QttyQuantity" = "qtty_quantity_t"
85
+ "QttyDerivedQuantity" = "qtty_derived_quantity_t"
86
+
87
+ [fn]
88
+ rename_args = "SnakeCase"
89
+
90
+ [struct]
91
+ rename_fields = "SnakeCase"
92
+
93
+ [enum]
94
+ rename_variants = "ScreamingSnakeCase"
95
+ prefix_with_name = true
96
+
97
+ [parse]
98
+ parse_deps = false
99
+ include = ["qtty-ffi"]
100
+
101
+ [parse.expand]
102
+ crates = ["qtty-ffi"]
103
+
104
+ [macro_expansion]
105
+ bitflags = false