can-api-generator 2.3.0__tar.gz → 2.5.0__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.
Files changed (107) hide show
  1. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/PKG-INFO +40 -1
  2. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/README.md +38 -0
  3. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/pyproject.toml +1 -0
  4. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/__about__.py +1 -1
  5. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/main.py +70 -22
  6. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/templates/rx_macros.jinja +1 -1
  7. can_api_generator-2.5.0/tests/test_config_file.py +68 -0
  8. can_api_generator-2.5.0/tests/test_time_callback_arg.py +107 -0
  9. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/.gitignore +0 -0
  10. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/Advantics_Bootloader.kcd +0 -0
  11. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/LICENSE.txt +0 -0
  12. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/manual_api_test.c +0 -0
  13. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/manual_api_test.h +0 -0
  14. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/manual_api_test.o +0 -0
  15. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/manual_cag_test.py +0 -0
  16. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/__init__.py +0 -0
  17. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/cbdb_generator.py +0 -0
  18. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/code_generator.py +0 -0
  19. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/msg_processor.py +0 -0
  20. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/templates/callback_macros.jinja +0 -0
  21. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/templates/canframe.jinja +0 -0
  22. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/templates/declaration_macros.jinja +0 -0
  23. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/templates/dyndec_header.jinja +0 -0
  24. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/templates/dyndec_source.jinja +0 -0
  25. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/templates/encoding_macros.jinja +0 -0
  26. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/templates/header_template.jinja +0 -0
  27. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/templates/source_template.jinja +0 -0
  28. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/templates/tx_macros.jinja +0 -0
  29. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/types.py +0 -0
  30. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/src/can_api_generator/utils.py +0 -0
  31. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/test_1_rx_over_can +0 -0
  32. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/test_api.c +0 -0
  33. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/test_api.h +0 -0
  34. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/test_kcd_api_test_mux_with_same_signal_names_labeled_mux_variants.c +0 -0
  35. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/test_mux_with_same_signal_names_labeled_mux_variants.c +0 -0
  36. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/test_mux_with_same_signal_names_labeled_mux_variants.h +0 -0
  37. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/test_test_1_rx_over_can.c +0 -0
  38. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/test_test_basic_multiplex_rx_over_can.c +0 -0
  39. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/test_test_mux_with_partially_labled_index_rx_over_can.c +0 -0
  40. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/test_test_mux_with_same_signal_names_labeled_mux_variants_rx_over_can.c +0 -0
  41. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/__init__.py +0 -0
  42. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/cantool_test_utils.jinja +0 -0
  43. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/compat_shim.jinja +0 -0
  44. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/dyndec_test_harness.c +0 -0
  45. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/multiple_busses_test.kcd +0 -0
  46. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_1.kcd +0 -0
  47. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_basic_multiplex.kcd +0 -0
  48. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_big_endian.kcd +0 -0
  49. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_can_db_model_dep.py +0 -0
  50. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_cbdb_generator.py +0 -0
  51. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_cli.py +0 -0
  52. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_cross_interface_compat.jinja +0 -0
  53. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_cross_interface_compat.py +0 -0
  54. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_dyndec_round_trip.py +0 -0
  55. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_float_wrong_length.kcd +0 -0
  56. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_message_parser.py +0 -0
  57. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_missing_consumer.kcd +0 -0
  58. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_missing_producer.kcd +0 -0
  59. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_mux_group_count_out_of_order.kcd +0 -0
  60. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_mux_with_labeled_index.kcd +0 -0
  61. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_mux_with_partially_labled_index.kcd +0 -0
  62. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_mux_with_same_signal_names.kcd +0 -0
  63. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_mux_with_same_signal_names_labeled_mux_variants.kcd +0 -0
  64. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_mux_with_scale_and_offset.kcd +0 -0
  65. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_non_encodable_0.kcd +0 -0
  66. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_overlapping_signals.kcd +0 -0
  67. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_periodic_tx_over_can.jinja +0 -0
  68. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_procedures.jinja +0 -0
  69. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_prog_source_template.jinja +0 -0
  70. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_rx_over_can.jinja +0 -0
  71. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_direction.kcd +0 -0
  72. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/double_plain.kcd +0 -0
  73. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/double_scaled.kcd +0 -0
  74. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/double_scaled_offset.kcd +0 -0
  75. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/float_plain.kcd +0 -0
  76. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/float_scaled.kcd +0 -0
  77. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/float_scaled_offset.kcd +0 -0
  78. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/sint_matching_fractional_scaled.kcd +0 -0
  79. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/sint_matching_fractional_scaled_offset.kcd +0 -0
  80. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/sint_matching_plain.kcd +0 -0
  81. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/sint_matching_scaled.kcd +0 -0
  82. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/sint_matching_scaled_offset.kcd +0 -0
  83. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/sint_nonmatching_fractional_scaled.kcd +0 -0
  84. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/sint_nonmatching_plain.kcd +0 -0
  85. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/sint_nonmatching_scaled.kcd +0 -0
  86. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/sint_nonmatching_scaled_offset.kcd +0 -0
  87. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/test_decoding_numerical_value.jinja +0 -0
  88. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/test_encoding_numerical_value.jinja +0 -0
  89. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/test_signal_encoding_wrong_float_size.kcd +0 -0
  90. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/uint_matching_fractional_scaled.kcd +0 -0
  91. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/uint_matching_plain.kcd +0 -0
  92. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/uint_matching_scaled.kcd +0 -0
  93. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/uint_matching_scaled_offset.kcd +0 -0
  94. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/uint_nonmatching_fractional_scaled.kcd +0 -0
  95. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/uint_nonmatching_plain.kcd +0 -0
  96. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/uint_nonmatching_scaled.kcd +0 -0
  97. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_encoding/uint_nonmatching_scaled_offset.kcd +0 -0
  98. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_signal_info.kcd +0 -0
  99. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_too_large_message.kcd +0 -0
  100. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_tx_function.py +0 -0
  101. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_tx_over_can.jinja +0 -0
  102. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_utils.py +0 -0
  103. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_vcan_round_trip.py +0 -0
  104. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_yaml_cbdb.py +0 -0
  105. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_yaml_input.py +0 -0
  106. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/tests/test_yaml_kcd_equivalence.py +0 -0
  107. {can_api_generator-2.3.0 → can_api_generator-2.5.0}/todo.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: can-api-generator
3
- Version: 2.3.0
3
+ Version: 2.5.0
4
4
  Summary: Program to generate a 'signal' style CAN API in C
5
5
  Project-URL: Documentation, https://sr.ht/~laplace/can-api-generator#readme
6
6
  Project-URL: Issues, https://sr.ht/~laplace/can-api-generator/issues
@@ -20,6 +20,7 @@ Requires-Dist: can-db-model>=0.1
20
20
  Requires-Dist: cantools
21
21
  Requires-Dist: click
22
22
  Requires-Dist: jinja2
23
+ Requires-Dist: pyyaml
23
24
  Requires-Dist: rich
24
25
  Description-Content-Type: text/markdown
25
26
 
@@ -45,6 +46,7 @@ Description-Content-Type: text/markdown
45
46
  - [Double-buffered signal access](#double-buffered-signal-access)
46
47
  - [RX/TX callback context](#rxtx-callback-context)
47
48
  - [Message signals as RX callback argument](#message-signals-as-rx-callback-argument)
49
+ - [Current time as callback argument](#current-time-as-callback-argument)
48
50
  - [Using your own frame type](#using-your-own-frame-type)
49
51
  - [Example Use](#example-use)
50
52
  - [Dynamic decoding](#dynamic-decoding)
@@ -55,6 +57,7 @@ Description-Content-Type: text/markdown
55
57
  - [`api`](#api)
56
58
  - [`cbdb`](#cbdb)
57
59
  - [`dyndec`](#dyndec)
60
+ - [Config file](#config-file)
58
61
 
59
62
  ## Installation
60
63
 
@@ -353,6 +356,17 @@ Passing `--msg-as-rx-callback-arg` instead hands the callback a `void *` pointin
353
356
 
354
357
  `struct MsgRxContext` remains a single shared type, which is why the signal struct is passed as `void *` rather than a typed pointer.
355
358
 
359
+ ### Current time as callback argument
360
+
361
+ The `now` handed to `<api>_process_received_frame` and `<api>_periodic_tx` can be forwarded to the callbacks, so a callback that timestamps, filters or rate-limits
362
+ does not have to read the clock again (and sees exactly the time the API used for `last_rx` / `last_tx`). The two paths have separate flags:
363
+
364
+ * `--time-as-rx-callback-arg` adds a `uint64_t now` parameter to the RX callback.
365
+ * `--time-as-tx-callback-arg` adds a `uint64_t now` parameter to the TX callback.
366
+
367
+ Arguments are always in the order message pointer, time, context pointer, each present only when its flag is set. With every RX flag the callback is
368
+ `void (*rx_callback)(void *msg, uint64_t now, void *ctx)`; with only `--time-as-tx-callback-arg` it is `void *(*tx_callback)(uint64_t now)`.
369
+
356
370
  ### Using your own frame type
357
371
 
358
372
  By default every generated function takes a `CanFrame *`, and the generator writes a sibling `canframe.h` that defines `CanFrame`. If the application already has a
@@ -598,6 +612,7 @@ to a mux group. The message list is followed immediately by that message's signa
598
612
  ## Command-line reference
599
613
 
600
614
  All generation is done through subcommands of `can-api-gen`. Run `can-api-gen COMMAND --help` for the authoritative option list.
615
+ Every command also accepts `--config FILE`; see [Config file](#config-file).
601
616
 
602
617
  ### `api`
603
618
 
@@ -643,6 +658,10 @@ Options:
643
658
  `--tx-signal-store` the pointer is the message's store struct, which the callback edits in place before encode; in callback-only mode `<api>_periodic_tx` stack-allocates
644
659
  the struct, and the callback fills it and returns it (or `NULL` to skip), so no application-provided storage is needed. See [Periodic TX](#periodic-tx). Only meaningful
645
660
  together with `-p`.
661
+ * `--time-as-rx-callback-arg` — pass the `now` of `<api>_process_received_frame` to the RX callback as a `uint64_t`, after the message pointer and before the
662
+ context pointer. See [Current time as callback argument](#current-time-as-callback-argument). Only meaningful together with `-r`.
663
+ * `--time-as-tx-callback-arg` — pass the `now` of `<api>_periodic_tx` to the TX callback as a `uint64_t`, after the message pointer and before the context
664
+ pointer. See [Current time as callback argument](#current-time-as-callback-argument). Only meaningful together with `-p`.
646
665
  * `--tx-function` — bind a `uint16_t (*tx_function)(void *, const <frame> *)` and its context to the periodic-TX API through `<api>_periodic_tx_init`;
647
666
  `<api>_periodic_tx(tx_signals, now)` then queues every due message through it in one pass and returns the number accepted. A 0 return leaves `last_tx` and
648
667
  `send_now` untouched so the message is retried. See [Queueing frames through a bound TX function](#queueing-frames-through-a-bound-tx-function). Only meaningful
@@ -699,3 +718,23 @@ Options:
699
718
  * `-lm`, `--length-member-name TEXT` — override the name of the `CanFrame` length member (default `length`).
700
719
  * `-pm`, `--payload-member-name TEXT` — override the name of the `CanFrame` payload member (default `payload`).
701
720
  * `--help` — show the CLI help and exit.
721
+
722
+ ### Config file
723
+
724
+ Instead of repeating a long flag list, put the options in a YAML file and pass it with `--config FILE` to any command. The file has one section per command, so
725
+ one file can serve `api`, `cbdb` and `dyndec`; a command reads only its own section. Keys are the long option names without the leading `--` (dashes or
726
+ underscores both work), and values are the option values (`true`/`false` for flags):
727
+
728
+ ```yaml
729
+ api:
730
+ periodic-tx: true
731
+ tx-signal-store: true
732
+ time-as-tx-callback-arg: true
733
+ id-member-name: can_id
734
+ dyndec:
735
+ id-member-name: can_id
736
+ ```
737
+
738
+ Anything given on the command line overrides the file. Every flag has a `--no-` form for this, e.g.
739
+ `can-api-gen api --config can.yaml --no-tx-signal-store db.kcd my_api DUT`. Positional arguments (`DB_FILE`, `LIBRARY_NAME`, `NODE_NAME`, …) always come from
740
+ the command line. An unknown key or an invalid value is an error.
@@ -20,6 +20,7 @@
20
20
  - [Double-buffered signal access](#double-buffered-signal-access)
21
21
  - [RX/TX callback context](#rxtx-callback-context)
22
22
  - [Message signals as RX callback argument](#message-signals-as-rx-callback-argument)
23
+ - [Current time as callback argument](#current-time-as-callback-argument)
23
24
  - [Using your own frame type](#using-your-own-frame-type)
24
25
  - [Example Use](#example-use)
25
26
  - [Dynamic decoding](#dynamic-decoding)
@@ -30,6 +31,7 @@
30
31
  - [`api`](#api)
31
32
  - [`cbdb`](#cbdb)
32
33
  - [`dyndec`](#dyndec)
34
+ - [Config file](#config-file)
33
35
 
34
36
  ## Installation
35
37
 
@@ -328,6 +330,17 @@ Passing `--msg-as-rx-callback-arg` instead hands the callback a `void *` pointin
328
330
 
329
331
  `struct MsgRxContext` remains a single shared type, which is why the signal struct is passed as `void *` rather than a typed pointer.
330
332
 
333
+ ### Current time as callback argument
334
+
335
+ The `now` handed to `<api>_process_received_frame` and `<api>_periodic_tx` can be forwarded to the callbacks, so a callback that timestamps, filters or rate-limits
336
+ does not have to read the clock again (and sees exactly the time the API used for `last_rx` / `last_tx`). The two paths have separate flags:
337
+
338
+ * `--time-as-rx-callback-arg` adds a `uint64_t now` parameter to the RX callback.
339
+ * `--time-as-tx-callback-arg` adds a `uint64_t now` parameter to the TX callback.
340
+
341
+ Arguments are always in the order message pointer, time, context pointer, each present only when its flag is set. With every RX flag the callback is
342
+ `void (*rx_callback)(void *msg, uint64_t now, void *ctx)`; with only `--time-as-tx-callback-arg` it is `void *(*tx_callback)(uint64_t now)`.
343
+
331
344
  ### Using your own frame type
332
345
 
333
346
  By default every generated function takes a `CanFrame *`, and the generator writes a sibling `canframe.h` that defines `CanFrame`. If the application already has a
@@ -573,6 +586,7 @@ to a mux group. The message list is followed immediately by that message's signa
573
586
  ## Command-line reference
574
587
 
575
588
  All generation is done through subcommands of `can-api-gen`. Run `can-api-gen COMMAND --help` for the authoritative option list.
589
+ Every command also accepts `--config FILE`; see [Config file](#config-file).
576
590
 
577
591
  ### `api`
578
592
 
@@ -618,6 +632,10 @@ Options:
618
632
  `--tx-signal-store` the pointer is the message's store struct, which the callback edits in place before encode; in callback-only mode `<api>_periodic_tx` stack-allocates
619
633
  the struct, and the callback fills it and returns it (or `NULL` to skip), so no application-provided storage is needed. See [Periodic TX](#periodic-tx). Only meaningful
620
634
  together with `-p`.
635
+ * `--time-as-rx-callback-arg` — pass the `now` of `<api>_process_received_frame` to the RX callback as a `uint64_t`, after the message pointer and before the
636
+ context pointer. See [Current time as callback argument](#current-time-as-callback-argument). Only meaningful together with `-r`.
637
+ * `--time-as-tx-callback-arg` — pass the `now` of `<api>_periodic_tx` to the TX callback as a `uint64_t`, after the message pointer and before the context
638
+ pointer. See [Current time as callback argument](#current-time-as-callback-argument). Only meaningful together with `-p`.
621
639
  * `--tx-function` — bind a `uint16_t (*tx_function)(void *, const <frame> *)` and its context to the periodic-TX API through `<api>_periodic_tx_init`;
622
640
  `<api>_periodic_tx(tx_signals, now)` then queues every due message through it in one pass and returns the number accepted. A 0 return leaves `last_tx` and
623
641
  `send_now` untouched so the message is retried. See [Queueing frames through a bound TX function](#queueing-frames-through-a-bound-tx-function). Only meaningful
@@ -674,3 +692,23 @@ Options:
674
692
  * `-lm`, `--length-member-name TEXT` — override the name of the `CanFrame` length member (default `length`).
675
693
  * `-pm`, `--payload-member-name TEXT` — override the name of the `CanFrame` payload member (default `payload`).
676
694
  * `--help` — show the CLI help and exit.
695
+
696
+ ### Config file
697
+
698
+ Instead of repeating a long flag list, put the options in a YAML file and pass it with `--config FILE` to any command. The file has one section per command, so
699
+ one file can serve `api`, `cbdb` and `dyndec`; a command reads only its own section. Keys are the long option names without the leading `--` (dashes or
700
+ underscores both work), and values are the option values (`true`/`false` for flags):
701
+
702
+ ```yaml
703
+ api:
704
+ periodic-tx: true
705
+ tx-signal-store: true
706
+ time-as-tx-callback-arg: true
707
+ id-member-name: can_id
708
+ dyndec:
709
+ id-member-name: can_id
710
+ ```
711
+
712
+ Anything given on the command line overrides the file. Every flag has a `--no-` form for this, e.g.
713
+ `can-api-gen api --config can.yaml --no-tx-signal-store db.kcd my_api DUT`. Positional arguments (`DB_FILE`, `LIBRARY_NAME`, `NODE_NAME`, …) always come from
714
+ the command line. An unknown key or an invalid value is an error.
@@ -28,6 +28,7 @@ dependencies = [
28
28
  'jinja2',
29
29
  'click',
30
30
  'rich',
31
+ 'pyyaml',
31
32
  ]
32
33
 
33
34
  [project.urls]
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2025-present Alexande Becker <nabla.becker@mailbox.org>
2
2
  #
3
3
  # SPDX-License-Identifier: LGPL-3.0-only
4
- __version__ = "2.3.0"
4
+ __version__ = "2.5.0"
@@ -12,6 +12,51 @@ from can_api_generator.cbdb_generator import generate_cbdb, build_sidecar, CbdbG
12
12
  from can_api_generator.utils import converrt_to_c_identifier
13
13
  from jinja2 import Environment, FileSystemLoader
14
14
  from pathlib import Path
15
+ import yaml
16
+
17
+
18
+ def _load_config(ctx, param, value):
19
+ """Turn the command's section of a YAML config file into click defaults.
20
+
21
+ The file maps command names to option tables, e.g. {"api": {"periodic-tx":
22
+ true}}. Keys are long option names (dashes or underscores). The values
23
+ become ctx.default_map, so every option given on the command line still
24
+ takes precedence over the file.
25
+ """
26
+ if value is None:
27
+ return
28
+ try:
29
+ with open(value) as config_file:
30
+ data = yaml.safe_load(config_file)
31
+ except (OSError, yaml.YAMLError) as e:
32
+ raise click.BadParameter(f"cannot read config file: {e}", ctx, param)
33
+ if data is None:
34
+ data = {}
35
+ if not isinstance(data, dict):
36
+ raise click.BadParameter(
37
+ "the config file must map command names to option tables", ctx, param)
38
+ section = data.get(ctx.info_name) or {}
39
+ if not isinstance(section, dict):
40
+ raise click.BadParameter(
41
+ f"section '{ctx.info_name}' must map option names to values", ctx, param)
42
+ known = {p.name for p in ctx.command.params
43
+ if isinstance(p, click.Option) and p.name != "config"}
44
+ defaults = {}
45
+ for key, option_value in section.items():
46
+ name = str(key).replace("-", "_")
47
+ if name not in known:
48
+ raise click.BadParameter(
49
+ f"unknown option '{key}' in section '{ctx.info_name}'", ctx, param)
50
+ defaults[name] = option_value
51
+ ctx.default_map = {**(ctx.default_map or {}), **defaults}
52
+
53
+
54
+ config_option = click.option(
55
+ "--config", type=click.Path(exists=True, dir_okay=False),
56
+ is_eager=True, expose_value=False, callback=_load_config,
57
+ help="YAML file providing defaults for this command's options, under a "
58
+ "section named after the command (api, cbdb, dyndec). Options given "
59
+ "on the command line override the file.")
15
60
 
16
61
 
17
62
  @click.group()
@@ -23,13 +68,14 @@ def main():
23
68
 
24
69
 
25
70
  @main.command()
71
+ @config_option
26
72
  @click.argument("DB_File", type=click.Path(exists=True, dir_okay=False),
27
73
  required=True, nargs=1)
28
74
  @click.argument("Library_Name", type=str, required=True)
29
75
  @click.argument("Node_Name", type=str, required=True)
30
- @click.option("-f", "--force", is_flag=True, required=False, default=False,
76
+ @click.option("-f", "--force/--no-force", required=False, default=False,
31
77
  help="Overwrite existing output files")
32
- @click.option("-a", "--encode-all-signals-func", is_flag=True, default=False,
78
+ @click.option("-a", "--encode-all-signals-func/--no-encode-all-signals-func", default=False,
33
79
  help="Generate a function for each message that encodes every signal into frames at least once,"
34
80
  " this is helpful when some signals are inside of a mux, but all signals need to be sent "
35
81
  " regardless of how many frames need to be encoded to send the state of each signal at least"
@@ -50,74 +96,74 @@ def main():
50
96
  @click.option("-pm", "--payload-member-name", type=str, default="payload",
51
97
  help="The name of the CanFrame struct member that holds the "
52
98
  "payload as uint64")
53
- @click.option("-r", "--global-rx", is_flag=True, default=False,
99
+ @click.option("-r", "--global-rx/--no-global-rx", default=False,
54
100
  help="Generate a function and api-wide struct that updates an"
55
101
  " api message struct upon reception of a matching frame")
56
- @click.option("-p", "--periodic-tx", is_flag=True, default=False,
102
+ @click.option("-p", "--periodic-tx/--no-periodic-tx", default=False,
57
103
  help="Generate code that will define a TX struct as well as a function "
58
104
  "that periodically sends the messages according to their "
59
105
  "periodicity")
60
- @click.option("--ti-compatible", is_flag=True, default=False,
106
+ @click.option("--ti-compatible/--no-ti-compatible", default=False,
61
107
  help="Generate C code that can be compiled using the TI C2000 compiler"
62
108
  " The TI compiler does not handle variable declarations inside of case"
63
109
  " blocks so the variable declaration is moved to the top of a switch")
64
- @click.option("--send-now", is_flag=True, default=False,
110
+ @click.option("--send-now/--no-send-now", default=False,
65
111
  help="Add mechanism in to the periodic tx function that allows to immediately "
66
112
  "send a message even if it is not enabled or has just been sent."
67
113
  " This is useful for updating critical signals as early as possible after a change")
68
- @click.option("--double-buffered", is_flag=True, default=False,
114
+ @click.option("--double-buffered/--no-double-buffered", default=False,
69
115
  help="Generate double-buffered signal storage so that an ISR can safely decode"
70
116
  " RX frames without exposing a partially-decoded struct to readers, and so"
71
117
  " that user code can stage TX signal updates without racing the periodic TX"
72
118
  " encoder. Swaps are performed via per-message pointer swaps; an explicit"
73
119
  " <api>_<msg>_commit call is required to publish staged TX values.")
74
- @click.option("--rx-callback-context", is_flag=True, default=False,
120
+ @click.option("--rx-callback-context/--no-rx-callback-context", default=False,
75
121
  help="Add a void *rx_callback_context field to MsgRxContext and "
76
122
  "pass it as the sole argument to the RX callback, widening "
77
123
  "its signature to void (*rx_callback)(void *).")
78
- @click.option("--tx-callback-context", is_flag=True, default=False,
124
+ @click.option("--tx-callback-context/--no-tx-callback-context", default=False,
79
125
  help="Add a void *tx_callback_context field to MsgTxContext and "
80
126
  "pass it as the sole argument to the TX callback, widening "
81
127
  "its signature to void (*tx_callback)(void *).")
82
- @click.option("--msg-as-rx-callback-arg", is_flag=True, default=False,
128
+ @click.option("--msg-as-rx-callback-arg/--no-msg-as-rx-callback-arg", default=False,
83
129
  help="Pass a void * to the message's just-decoded signal struct "
84
130
  "as the first argument to the RX callback. Composes with "
85
131
  "--rx-callback-context, which appends the context pointer as "
86
132
  "a second argument. Only meaningful together with -r.")
87
- @click.option("--rx-signal-store", is_flag=True, default=False,
133
+ @click.option("--rx-signal-store/--no-rx-signal-store", default=False,
88
134
  help="Generate a permanent per-message RX signal store in the "
89
135
  "global RX struct (the default before v2.0). Without it, "
90
136
  "global RX is callback-only: decoded signals reach the "
91
137
  "application solely through the per-message RX callback and "
92
138
  "are never stored. Only meaningful together with -r.")
93
- @click.option("--with-rx-valid", is_flag=True, default=False,
139
+ @click.option("--with-rx-valid/--no-with-rx-valid", default=False,
94
140
  help="In callback-only mode (no --rx-signal-store), add a valid "
95
141
  "field to MsgRxContext and set it on each matched frame, for "
96
142
  "struct-layout compatibility with store-based APIs. No effect "
97
143
  "when --rx-signal-store is set.")
98
- @click.option("--msg-as-tx-callback-arg", is_flag=True, default=False,
144
+ @click.option("--msg-as-tx-callback-arg/--no-msg-as-tx-callback-arg", default=False,
99
145
  help="Pass a void * to the message's store struct (the one about "
100
146
  "to be encoded) as the first argument to the TX callback. "
101
147
  "Composes with --tx-callback-context, which appends the "
102
148
  "context pointer as a second argument. Only meaningful "
103
149
  "together with -p.")
104
- @click.option("--tx-signal-store", is_flag=True, default=False,
150
+ @click.option("--tx-signal-store/--no-tx-signal-store", default=False,
105
151
  help="Generate a permanent per-message TX signal store in the "
106
152
  "periodic-TX struct (the default before v2.0). Without it, "
107
153
  "periodic TX is callback-only: each scheduled message's "
108
154
  "callback returns the struct to encode, and a NULL return or "
109
155
  "missing callback sends nothing. Only meaningful with -p.")
110
- @click.option("--time-as-rx-callback-arg", is_flag=True, default=False,
156
+ @click.option("--time-as-rx-callback-arg/--no-time-as-rx-callback-arg", default=False,
111
157
  help="Pass the current time (the now argument of "
112
158
  "<api>_process_received_frame) to the RX callback as a "
113
159
  "uint64_t, after the message pointer and before the context "
114
160
  "pointer. Only meaningful together with -r.")
115
- @click.option("--time-as-tx-callback-arg", is_flag=True, default=False,
161
+ @click.option("--time-as-tx-callback-arg/--no-time-as-tx-callback-arg", default=False,
116
162
  help="Pass the current time (the now argument of "
117
163
  "<api>_periodic_tx) to the TX callback as a uint64_t, after "
118
164
  "the message pointer and before the context pointer. Only "
119
165
  "meaningful together with -p.")
120
- @click.option("--tx-function", is_flag=True, default=False,
166
+ @click.option("--tx-function/--no-tx-function", default=False,
121
167
  help="Bind a frame sink to the periodic-TX API instead of "
122
168
  "returning one frame per call. <api>_periodic_tx_init takes "
123
169
  "a uint16_t (*tx_function)(void *, const <frame> *) and its "
@@ -125,18 +171,18 @@ def main():
125
171
  "due message to it in a single pass and returns the number "
126
172
  "accepted. A 0 return leaves last_tx and send_now untouched, "
127
173
  "so the message is retried. Only meaningful with -p.")
128
- @click.option("--prefix-signal-struct-names", is_flag=True, default=False,
174
+ @click.option("--prefix-signal-struct-names/--no-prefix-signal-struct-names", default=False,
129
175
  help="Prefix every per-message signal struct type name with the "
130
176
  "CamelCase API prefix (the same prefix used for "
131
177
  "<Prefix>RxSignals / <Prefix>TxSignals), so two similar APIs "
132
178
  "(e.g. two versions of a database) can coexist in one "
133
179
  "translation unit with distinct struct names.")
134
- @click.option("--prefix-context-struct-names", is_flag=True, default=False,
180
+ @click.option("--prefix-context-struct-names/--no-prefix-context-struct-names", default=False,
135
181
  help="Prefix the MsgRxContext / MsgTxContext struct type names (and "
136
182
  "their include-guard tokens) with the CamelCase API prefix, so "
137
183
  "context structs from two coexisting APIs do not share one "
138
184
  "definition.")
139
- @click.option("-v", "--verbose", is_flag=True, default=False,
185
+ @click.option("-v", "--verbose/--no-verbose", default=False,
140
186
  help="Print the Data that is passed to the template that "
141
187
  "generates the code")
142
188
  def api(db_file, library_name, node_name, force, encode_all_signals_func,
@@ -228,10 +274,11 @@ def api(db_file, library_name, node_name, force, encode_all_signals_func,
228
274
 
229
275
 
230
276
  @main.command()
277
+ @config_option
231
278
  @click.argument("DB_File", type=click.Path(exists=True, dir_okay=False),
232
279
  required=True, nargs=1)
233
280
  @click.argument("Output_Base", type=str, required=True)
234
- @click.option("-f", "--force", is_flag=True, required=False, default=False,
281
+ @click.option("-f", "--force/--no-force", required=False, default=False,
235
282
  help="Overwrite existing output files")
236
283
  def cbdb(db_file, output_base, force):
237
284
  """
@@ -267,8 +314,9 @@ def cbdb(db_file, output_base, force):
267
314
 
268
315
 
269
316
  @main.command()
317
+ @config_option
270
318
  @click.argument("Library_Name", type=str, required=True)
271
- @click.option("-f", "--force", is_flag=True, required=False, default=False,
319
+ @click.option("-f", "--force/--no-force", required=False, default=False,
272
320
  help="Overwrite existing output files")
273
321
  @click.option("-im", "--id-member-name", type=str, default="arbitration_id",
274
322
  help="The name of the CanFrame struct member that holds the "
@@ -68,7 +68,7 @@ struct {{ api_struct_prefix }}RxSignals {{ var_name }};
68
68
  {% endmacro %}
69
69
 
70
70
  {% macro generate_global_rx_frame_process_function(messages, api_name, api_struct_prefix, can_frame_type, return_type, time_type, rx_callback, double_buffered=False, rx_callback_context=false, msg_as_rx_callback_arg=false, rx_signal_store=false, with_rx_valid=false, ctx_name='MsgRxContext', time_as_rx_callback_arg=false) -%}
71
- {%- set time_arg = 'now' if time_as_rx_callback_arg else '' %}
71
+ {%- set time_arg = 'now' if time_as_rx_callback_arg else '' -%}
72
72
  {{ declare_global_rx_frame_process_function(api_name, api_struct_prefix, can_frame_type, return_type, time_type) }} {
73
73
  {%- for message in messages %}
74
74
  {%- if message.direction == 'rx' or message.direction == 'rt' %}
@@ -0,0 +1,68 @@
1
+ """--config: YAML defaults per command, overridden by the command line."""
2
+ from pathlib import Path
3
+
4
+ from click.testing import CliRunner
5
+
6
+ from src.can_api_generator.main import main
7
+
8
+ CONFIG = """\
9
+ api:
10
+ periodic-tx: true
11
+ tx_signal_store: true
12
+ time-as-tx-callback-arg: true
13
+ id-member-name: can_id
14
+ """
15
+
16
+
17
+ def _run(tmp_path, monkeypatch, config, *args):
18
+ kcd = str(Path("tests/test_1.kcd").resolve())
19
+ monkeypatch.chdir(tmp_path)
20
+ (tmp_path / "cfg.yaml").write_text(config)
21
+ return CliRunner().invoke(
22
+ main, ["api", "--config", "cfg.yaml", kcd, "cf", "DUT", "-f", *args])
23
+
24
+
25
+ def test_config_provides_options(tmp_path, monkeypatch):
26
+ result = _run(tmp_path, monkeypatch, CONFIG)
27
+ assert result.exit_code == 0, result.output
28
+ header = (tmp_path / "cf.h").read_text()
29
+ source = (tmp_path / "cf.c").read_text()
30
+ assert "struct Message2 message_2_signals;" in header
31
+ assert "void *(*tx_callback)(uint64_t);" in header
32
+ assert "output_frame->can_id = 0x1;" in source
33
+
34
+
35
+ def test_command_line_overrides_config(tmp_path, monkeypatch):
36
+ result = _run(tmp_path, monkeypatch, CONFIG,
37
+ "--no-tx-signal-store", "-im", "arb")
38
+ assert result.exit_code == 0, result.output
39
+ header = (tmp_path / "cf.h").read_text()
40
+ source = (tmp_path / "cf.c").read_text()
41
+ assert "message_2_signals" not in header
42
+ assert "output_frame->arb = 0x1;" in source
43
+ # untouched config values still apply
44
+ assert "(uint64_t)" in header
45
+
46
+
47
+ def test_config_section_of_other_command_ignored(tmp_path, monkeypatch):
48
+ result = _run(tmp_path, monkeypatch, "dyndec:\n force: true\n", "-p")
49
+ assert result.exit_code == 0, result.output
50
+ assert "arbitration_id" in (tmp_path / "cf.c").read_text()
51
+
52
+
53
+ def test_config_unknown_option_rejected(tmp_path, monkeypatch):
54
+ result = _run(tmp_path, monkeypatch, "api:\n bogus-flag: true\n")
55
+ assert result.exit_code == 2
56
+ assert "unknown option 'bogus-flag'" in result.output
57
+
58
+
59
+ def test_config_invalid_value_rejected(tmp_path, monkeypatch):
60
+ result = _run(tmp_path, monkeypatch, "api:\n periodic-tx: maybe\n")
61
+ assert result.exit_code == 2
62
+ assert "periodic-tx" in result.output
63
+
64
+
65
+ def test_config_not_a_mapping_rejected(tmp_path, monkeypatch):
66
+ result = _run(tmp_path, monkeypatch, "- 1\n")
67
+ assert result.exit_code == 2
68
+ assert "must map command names" in result.output
@@ -0,0 +1,107 @@
1
+ """Runtime checks for --time-as-rx-callback-arg / --time-as-tx-callback-arg.
2
+
3
+ Generates the test_1.kcd API with the flag, links it against a C harness and
4
+ asserts that the callback receives the now passed to the API entry point, in
5
+ the documented argument order (message, now, context).
6
+ """
7
+ import subprocess
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+ from click.testing import CliRunner
12
+
13
+ from src.can_api_generator.main import main
14
+
15
+ RX_HARNESS = r"""
16
+ #include <assert.h>
17
+ #include <stdio.h>
18
+ #include "tc.h"
19
+
20
+ static uint64_t seen_now;
21
+ static int ctx_token;
22
+
23
+ static void rx_cb(void *msg, uint64_t now, void *ctx) {
24
+ assert(msg != 0);
25
+ assert(ctx == &ctx_token);
26
+ seen_now = now;
27
+ }
28
+
29
+ int main(void) {
30
+ struct TcRxSignals rx = {0};
31
+ CanFrame frame = {.length = 4, .arbitration_id = 0x1, .payload = 0};
32
+ rx.message_2_context.rx_callback = rx_cb;
33
+ rx.message_2_context.rx_callback_context = &ctx_token;
34
+ assert(tc_process_received_frame(&rx, &frame, 12345) == 1);
35
+ assert(seen_now == 12345);
36
+ puts("ok");
37
+ return 0;
38
+ }
39
+ """
40
+
41
+ TX_HARNESS = r"""
42
+ #include <assert.h>
43
+ #include <stdio.h>
44
+ #include "tc.h"
45
+
46
+ static struct Message2 m2;
47
+ static uint64_t seen_now;
48
+ static int ctx_token;
49
+
50
+ static void *tx_cb(void *msg, uint64_t now, void *ctx) {
51
+ assert(msg != 0);
52
+ assert(ctx == &ctx_token);
53
+ seen_now = now;
54
+ return &m2;
55
+ }
56
+
57
+ int main(void) {
58
+ struct TcTxSignals tx;
59
+ CanFrame frame;
60
+ tc_periodic_tx_init(&tx, 0);
61
+ tx.message_2_context.tx_callback = tx_cb;
62
+ tx.message_2_context.tx_callback_context = &ctx_token;
63
+ tx.message_2_context.send_now = 1;
64
+ assert(tc_periodic_tx(&tx, &frame, 777) == 1);
65
+ assert(seen_now == 777);
66
+ puts("ok");
67
+ return 0;
68
+ }
69
+ """
70
+
71
+
72
+ @pytest.mark.parametrize("path", ["rx", "tx"])
73
+ def test_callback_receives_now(tmp_path, monkeypatch, path):
74
+ kcd = str(Path("tests/test_1.kcd").resolve())
75
+ monkeypatch.chdir(tmp_path)
76
+ if path == "rx":
77
+ flags = ["-r", "--time-as-rx-callback-arg", "--rx-callback-context"]
78
+ harness = RX_HARNESS
79
+ node = "other"
80
+ else:
81
+ flags = ["-p", "--time-as-tx-callback-arg", "--tx-callback-context",
82
+ "--msg-as-tx-callback-arg", "--send-now"]
83
+ harness = TX_HARNESS
84
+ node = "DUT"
85
+ result = CliRunner().invoke(main, ["api", kcd, "tc", node, *flags])
86
+ assert result.exit_code == 0, result.output
87
+ (tmp_path / "harness.c").write_text(harness)
88
+ build = subprocess.run(
89
+ ["gcc", "-Wall", "-Werror", "-o", "harness", "harness.c", "tc.c"],
90
+ capture_output=True, text=True)
91
+ assert build.returncode == 0, build.stderr
92
+ run = subprocess.run(["./harness"], capture_output=True, text=True)
93
+ assert run.returncode == 0, run.stdout + run.stderr
94
+ assert run.stdout.strip() == "ok"
95
+
96
+
97
+ @pytest.mark.parametrize("flag,cb", [
98
+ ("-r", "void (*rx_callback)(void *);"),
99
+ ("-p", "void *(*tx_callback)(void);"),
100
+ ])
101
+ def test_time_arg_absent_by_default(tmp_path, monkeypatch, flag, cb):
102
+ kcd = str(Path("tests/test_1.kcd").resolve())
103
+ monkeypatch.chdir(tmp_path)
104
+ node = "other" if flag == "-r" else "DUT"
105
+ result = CliRunner().invoke(main, ["api", kcd, "tc", node, flag])
106
+ assert result.exit_code == 0, result.output
107
+ assert cb in (tmp_path / "tc.h").read_text()