fixed some style issues, added tooling for docs
This commit is contained in:
9
docs/core/error.md
Normal file
9
docs/core/error.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# core/error.h
|
||||
|
||||
## Free functions
|
||||
|
||||
### `std::mutex &error_mutex() noexcept;`
|
||||
### `inline T&& show(std::string_view name, T&& value) noexcept { ... }`
|
||||
### `std::lock_guard<std::mutex> lock(error_mutex());`
|
||||
### `inline T&& eval_and_show(std::string_view expr, T&& value) noexcept { ... }`
|
||||
### `std::lock_guard<std::mutex> lock(error_mutex());`
|
||||
10
docs/core/host.md
Normal file
10
docs/core/host.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# core/host.h
|
||||
|
||||
## class NetworkNic — public interface
|
||||
|
||||
### `Host(Simulator *const sim, NodeId id) noexcept;`
|
||||
### `virtual ~Host() = default;`
|
||||
### `NetworkNic *nic() const noexcept { ... }`
|
||||
### `void attach_nic(NetworkNic* nic) noexcept;`
|
||||
### `void detach_nic(NetworkNic* nic) noexcept;`
|
||||
### `Host(const Host &) = delete;`
|
||||
13
docs/core/logger.md
Normal file
13
docs/core/logger.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# core/logger.h
|
||||
|
||||
## class Logger — public interface
|
||||
|
||||
### `Logger(std::string_view path, bool append) noexcept;`
|
||||
### `~Logger() noexcept;`
|
||||
### `Logger(const Logger &) = delete;`
|
||||
### `Logger(Logger &&) = delete;`
|
||||
### `bool is_open() const noexcept { ... }`
|
||||
### `void write_line(std::string_view line) noexcept;`
|
||||
### `void flush() noexcept;`
|
||||
### `void close() noexcept;`
|
||||
### `std::string_view path() const noexcept { ... }`
|
||||
13
docs/core/node.md
Normal file
13
docs/core/node.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# core/node.h
|
||||
|
||||
## class Node — public interface
|
||||
|
||||
### `Node(Simulator *const sim, NodeId id, NodeType type) noexcept;`
|
||||
### `virtual ~Node() = default;`
|
||||
### `NodeId id() const noexcept;`
|
||||
### `NodeStatus status() const noexcept;`
|
||||
### `NodeType type() const noexcept;`
|
||||
### `void set_status(NodeStatus s) noexcept;`
|
||||
### `void boot(Time boottime_ns);`
|
||||
### `void reboot(Time boottime_ns);`
|
||||
### `Node(const Node &) = delete;`
|
||||
16
docs/core/rng.md
Normal file
16
docs/core/rng.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# core/rng.h
|
||||
|
||||
## class Rng — public interface
|
||||
|
||||
### `: _eng(seed) { ... }`
|
||||
### `void seed(seed_type s) noexcept { ... }`
|
||||
### `_eng.seed(s);`
|
||||
### `double uniform01() { ... }`
|
||||
### `Int uniform_range(Int lo_inclusive, Int hi_exclusive) { ... }`
|
||||
### `Int uniform_range(Int hi_exclusive) { ... }`
|
||||
### `double uniform_range(double lo_inclusive, double hi_exclusive) { ... }`
|
||||
### `std::uint64_t poisson(double lambda) { ... }`
|
||||
### `T choose_weighted(const std::vector<std::pair<double, T>>& items) { ... }`
|
||||
### `return choose_weighted_impl(items.begin(), items.end());`
|
||||
### `T choose_weighted(std::initializer_list<std::pair<double, T>> items) { ... }`
|
||||
### `return choose_weighted_impl(items.begin(), items.end());`
|
||||
27
docs/core/simulator.md
Normal file
27
docs/core/simulator.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# core/simulator.h
|
||||
|
||||
## Free functions
|
||||
|
||||
### `std::numeric_limits<InstanceId>::max();`
|
||||
### `std::numeric_limits<LinkId>::max();`
|
||||
|
||||
## class Rng — public interface
|
||||
|
||||
### `Simulator() = default;`
|
||||
### `static std::pair<InstanceId, Simulator *> create_simulator(InstanceId id);`
|
||||
### `static Simulator *get_simulator(InstanceId id) noexcept;`
|
||||
### `Time now() const noexcept;`
|
||||
### `EventId schedule_at(Time abs_time, F&& f, Args&&... args) { ... }`
|
||||
### `_event_pq.push(std::move(it));`
|
||||
### `EventId schedule_after(Time delay, F&& f, Args&&... args) { ... }`
|
||||
### `bool cancel(EventId id);`
|
||||
### `bool run_next();`
|
||||
### `void run_until(Time end_time);`
|
||||
### `void lock() noexcept;`
|
||||
### `bool is_locked() const noexcept { ... }`
|
||||
### `void flush_after(Time grace) noexcept;`
|
||||
### `Rng* create_rng(std::uint64_t seed);`
|
||||
### `Rng* get_rng() noexcept;`
|
||||
### `Rng const* get_rng() const noexcept;`
|
||||
### `Link* get_link(LinkId id) noexcept;`
|
||||
### `Link const* get_link(LinkId id) const noexcept;`
|
||||
36
docs/core/time.md
Normal file
36
docs/core/time.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# core/time.h
|
||||
|
||||
## Free functions
|
||||
|
||||
### `constexpr Time operator""_ns(unsigned long long v) noexcept { ... }`
|
||||
### `return Time::from_ns(static_cast<Time::rep>(v));`
|
||||
### `constexpr Time operator""_us(unsigned long long v) noexcept { ... }`
|
||||
### `return Time::from_us(static_cast<Time::rep>(v));`
|
||||
### `constexpr Time operator""_ms(unsigned long long v) noexcept { ... }`
|
||||
### `return Time::from_ms(static_cast<Time::rep>(v));`
|
||||
### `constexpr Time operator""_s (unsigned long long v) noexcept { ... }`
|
||||
### `return Time::from_s (static_cast<Time::rep>(v));`
|
||||
|
||||
## class Time — public interface
|
||||
|
||||
### `constexpr Time() : _nsec(0) { ... }`
|
||||
### `explicit constexpr Time(rep ns) : _nsec(ns) { ... }`
|
||||
### `static constexpr Time from_ns(rep ns) noexcept { ... }`
|
||||
### `return Time(ns);`
|
||||
### `static constexpr Time from_us(rep us) noexcept { ... }`
|
||||
### `return Time(us * 1000ULL);`
|
||||
### `static constexpr Time from_ms(rep ms) noexcept { ... }`
|
||||
### `return Time(ms * 1000ULL * 1000ULL);`
|
||||
### `static constexpr Time from_s (rep s ) noexcept { ... }`
|
||||
### `return Time(s * 1000ULL * 1000ULL * 1000ULL);`
|
||||
### `constexpr rep ns() const noexcept { ... }`
|
||||
### `constexpr rep count() const noexcept { ... }`
|
||||
### `static constexpr rep us_to_ns(rep us) noexcept { ... }`
|
||||
### `static constexpr rep ms_to_ns(rep ms) noexcept { ... }`
|
||||
### `return Time(a._nsec + b._nsec);`
|
||||
### `return Time(a._nsec * b._nsec);`
|
||||
### `return safe_sub(a, b);`
|
||||
### `if (a._nsec < b._nsec) { ... }`
|
||||
### `return Time(a._nsec - b._nsec);`
|
||||
### `constexpr Time unsafe_sub(Time t) const noexcept { ... }`
|
||||
### `return Time(this->_nsec - t._nsec);`
|
||||
13
docs/core/timer.md
Normal file
13
docs/core/timer.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# core/timer.h
|
||||
|
||||
## class Timer — public interface
|
||||
|
||||
### `Timer() { ... }`
|
||||
### `init();`
|
||||
### `void init() noexcept { ... }`
|
||||
### `_start = clock::now();`
|
||||
### `Time start() const noexcept { ... }`
|
||||
### `auto tp = _start.time_since_epoch();`
|
||||
### `Time now() const noexcept { ... }`
|
||||
### `auto tp = clock::now().time_since_epoch();`
|
||||
### `Time elapsed() const noexcept { ... }`
|
||||
6
docs/core/types.md
Normal file
6
docs/core/types.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# core/types.h
|
||||
|
||||
## Free functions
|
||||
|
||||
### `inline IPv4Addr ipv4(NodeId n, PortId p) noexcept { ... }`
|
||||
### `return (static_cast<uint32_t>(n) << 16) | static_cast<uint32_t>(p);`
|
||||
19
docs/hosts/mgmt_msg.md
Normal file
19
docs/hosts/mgmt_msg.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# hosts/mgmt_msg.h
|
||||
|
||||
## class MgmtMsg — public interface
|
||||
|
||||
### `virtual ~MgmtMsg() = default;`
|
||||
|
||||
## class HeartbeatMsg — public interface
|
||||
|
||||
### `: subscriber_id(sid), status(st), generated_at(t) { ... }`
|
||||
### `MgmtKind kind() const noexcept override { ... }`
|
||||
|
||||
## class JobFinishedMsg — public interface
|
||||
|
||||
### `: flow_id(fid), finished_at(t) { ... }`
|
||||
### `MgmtKind kind() const noexcept override { ... }`
|
||||
|
||||
## class EndSimulationMsg — public interface
|
||||
|
||||
### `MgmtKind kind() const noexcept override { ... }`
|
||||
20
docs/hosts/policies.md
Normal file
20
docs/hosts/policies.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# hosts/policies.h
|
||||
|
||||
## class PubBasePolicy — public interface
|
||||
|
||||
### `virtual ~PubBasePolicy() = default;`
|
||||
|
||||
## class PubRRPolicy — public interface
|
||||
|
||||
### `: _ranges(std::move(ranges)) { ... }`
|
||||
### `validate_and_build();`
|
||||
### `PacketGroups select_multicast_groups(PacketGroups update_groups_mask) override { ... }`
|
||||
### `for (auto const& r : _ranges) { ... }`
|
||||
|
||||
## class SubBasePolicy — public interface
|
||||
|
||||
### `virtual ~SubBasePolicy() = default;`
|
||||
|
||||
## class SubDummyPolicy — public interface
|
||||
|
||||
### `~SubDummyPolicy() override = default;`
|
||||
10
docs/hosts/publisher.md
Normal file
10
docs/hosts/publisher.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# hosts/publisher.h
|
||||
|
||||
## class Publisher — public interface
|
||||
|
||||
### `void recv_update(Bytes size, PacketGroups update_groups_mask) noexcept;`
|
||||
### `void set_status(NodeStatus s, Time new_latency = Time{}) noexcept;`
|
||||
### `virtual void recv_mgmt_msg(MgmtMsgPtr msg) noexcept override;`
|
||||
### `virtual void recv_frame(const Packet& frame) override;`
|
||||
### `uint64_t updates_in() const noexcept { ... }`
|
||||
### `uint64_t bytes_out() const noexcept { ... }`
|
||||
8
docs/hosts/subscriber.md
Normal file
8
docs/hosts/subscriber.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# hosts/subscriber.h
|
||||
|
||||
## class Publisher — public interface
|
||||
|
||||
### `virtual void recv_mgmt_msg(MgmtMsgPtr msg) noexcept override;`
|
||||
### `void recv_frame(const Packet& frame) override;`
|
||||
### `void set_status(NodeStatus s) noexcept;`
|
||||
### `void set_publisher(Publisher* p) noexcept { ... }`
|
||||
19
docs/network/link.md
Normal file
19
docs/network/link.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# network/link.h
|
||||
|
||||
## class Link — public interface
|
||||
|
||||
### `void send_pkt(Packet &pkt, NodeId caller);`
|
||||
### `void schedule_delivery_after(Packet &pkt, NodeId caller, Time after);`
|
||||
### `Time next_available(NodeId sender) const noexcept;`
|
||||
### `std::optional<Reservation> reserve(Bytes bytes, NodeId sender) noexcept;`
|
||||
### `Time serialization_time(Bytes bytes) const noexcept { ... }`
|
||||
### `return serialization_time(bytes, _bandwidth_gbps_cur);`
|
||||
### `Time propagation_latency() const noexcept { ... }`
|
||||
### `LinkId id() const noexcept { ... }`
|
||||
### `LinkStatus status() const noexcept { ... }`
|
||||
### `double bandwidth_gbps() const noexcept { ... }`
|
||||
### `NodeId src_id() const noexcept { ... }`
|
||||
### `NodeId dst_id() const noexcept { ... }`
|
||||
### `PortId src_port() const noexcept { ... }`
|
||||
### `PortId dst_port() const noexcept { ... }`
|
||||
### `static Time serialization_time(Bytes bytes, double gbps) noexcept;`
|
||||
11
docs/network/network_nic.md
Normal file
11
docs/network/network_nic.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# network/network_nic.h
|
||||
|
||||
## class NetworkNic — public interface
|
||||
|
||||
### `virtual void recv_pkt(Packet &pkt, PortId ingress) override;`
|
||||
### `void attach_host(Host* host) noexcept;`
|
||||
### `void detach_host(Host* host) noexcept;`
|
||||
### `void set_status(NodeStatus s, Time new_latency = Time(0)) noexcept;`
|
||||
### `const NicTelemetry &telemetry() const noexcept { ... }`
|
||||
### `void set_port_blacklisted(PortId port, bool blacklisted) noexcept;`
|
||||
### `bool is_port_blacklisted(PortId port) const noexcept;`
|
||||
6
docs/network/network_node.md
Normal file
6
docs/network/network_node.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# network/network_node.h
|
||||
|
||||
## class NetworkNode — public interface
|
||||
|
||||
### `explicit NetworkNode(Simulator *const sim, NodeId id, NodeType type) noexcept;`
|
||||
### `virtual ~NetworkNode() = default;`
|
||||
9
docs/network/network_switch.md
Normal file
9
docs/network/network_switch.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# network/network_switch.h
|
||||
|
||||
## class NetworkSwitch — public interface
|
||||
|
||||
### `virtual void recv_pkt(Packet &pkt, PortId ingress) override;`
|
||||
### `void set_status(NodeStatus s, Time new_forward_latency = Time(0)) noexcept;`
|
||||
### `NodeStatus get_status() const noexcept { ... }`
|
||||
### `return status();`
|
||||
### `uint16_t port_cnt() const noexcept { ... }`
|
||||
18
docs/network/nic/congestion_control.md
Normal file
18
docs/network/nic/congestion_control.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# network/nic/congestion_control.h
|
||||
|
||||
## class CongestionControl — public interface
|
||||
|
||||
### `explicit CongestionControl(Bytes init_cwnd, Bytes max_cwnd) noexcept;`
|
||||
### `virtual ~CongestionControl() = default;`
|
||||
### `Bytes cwnd() const noexcept { ... }`
|
||||
### `Bytes cwnd_max() const noexcept { ... }`
|
||||
|
||||
## class DCQCN — public interface
|
||||
|
||||
### `explicit DCQCN(Bytes init_cwnd, Bytes max_cwnd) noexcept;`
|
||||
### `virtual void update(const Packet& pkt, Time rtt) noexcept override;`
|
||||
|
||||
## class NSCC — public interface
|
||||
|
||||
### `explicit NSCC(Bytes init_cwnd, Bytes max_cwnd) noexcept;`
|
||||
### `virtual void update(const Packet& pkt, Time rtt) noexcept override;`
|
||||
12
docs/network/nic/load_balance.md
Normal file
12
docs/network/nic/load_balance.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# network/nic/load_balance.h
|
||||
|
||||
## class LoadBalance — public interface
|
||||
|
||||
### `explicit LoadBalance(Rng *const rng) noexcept : _rng(rng) { ... }`
|
||||
### `virtual ~LoadBalance() = default;`
|
||||
|
||||
## class LBRandomPacketSpraying — public interface
|
||||
|
||||
### `explicit LBRandomPacketSpraying(Rng *const rng) noexcept : LoadBalance(rng) { ... }`
|
||||
### `virtual void update(const Packet& pkt) noexcept override;`
|
||||
### `virtual uint16_t get_entropy(const Packet& context) noexcept override;`
|
||||
35
docs/network/packet.md
Normal file
35
docs/network/packet.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# network/packet.h
|
||||
|
||||
## class Packet — public interface
|
||||
|
||||
### `NodeId src_node() const noexcept;`
|
||||
### `PortId src_port() const noexcept;`
|
||||
### `NodeId dst_node() const noexcept;`
|
||||
### `PortId dst_port() const noexcept;`
|
||||
### `PacketProtocol protocol() const noexcept;`
|
||||
### `PacketSeq seq() const noexcept;`
|
||||
### `FlowId flow_id() const noexcept;`
|
||||
### `uint32_t entropy() const noexcept;`
|
||||
### `void set_src_node(NodeId n) noexcept;`
|
||||
### `void set_src_port(PortId p) noexcept;`
|
||||
### `void set_dst_node(NodeId n) noexcept;`
|
||||
### `void set_dst_port(PortId p) noexcept;`
|
||||
### `void set_seq(PacketSeq s) noexcept;`
|
||||
### `void set_flow_id(FlowId f) noexcept;`
|
||||
### `void set_entropy(uint32_t e) noexcept;`
|
||||
### `void set_protocol(PacketProtocol p) noexcept;`
|
||||
### `void set_payload_size(Bytes size) noexcept;`
|
||||
### `void set_ecn_enabled(bool v) noexcept;`
|
||||
### `void set_ecn_marked(bool v) noexcept;`
|
||||
### `void set_eof(bool v) noexcept;`
|
||||
### `bool is_ecn_enabled() const noexcept;`
|
||||
### `bool is_ecn() const noexcept;`
|
||||
### `bool is_eof() const noexcept;`
|
||||
### `FlowPriority priority() const noexcept;`
|
||||
### `uint8_t priority_raw() const noexcept;`
|
||||
### `Bytes header_size() const noexcept;`
|
||||
### `Bytes payload_size() const noexcept;`
|
||||
### `Bytes total_size() const noexcept;`
|
||||
### `PacketGroups groups() const noexcept;`
|
||||
### `void set_groups(PacketGroups g) noexcept;`
|
||||
### `void add_groups(PacketGroups gmask) noexcept;`
|
||||
5
docs/network/switch/dedicated_buffer.md
Normal file
5
docs/network/switch/dedicated_buffer.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# network/switch/dedicated_buffer.h
|
||||
|
||||
## class DedicatedBuffer — public interface
|
||||
|
||||
### `bool drain_one(PortId port) override;`
|
||||
5
docs/network/switch/ecn_dedicated_red.md
Normal file
5
docs/network/switch/ecn_dedicated_red.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# network/switch/ecn_dedicated_red.h
|
||||
|
||||
## class DedicatedREDEngine — public interface
|
||||
|
||||
### `_rng(rng) { ... }`
|
||||
5
docs/network/switch/ecn_engine.md
Normal file
5
docs/network/switch/ecn_engine.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# network/switch/ecn_engine.h
|
||||
|
||||
## class SwitchBuffer — public interface
|
||||
|
||||
### `virtual ~ECNEngine() = default;`
|
||||
5
docs/network/switch/ecn_shared_red.md
Normal file
5
docs/network/switch/ecn_shared_red.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# network/switch/ecn_shared_red.h
|
||||
|
||||
## class SharedREDEngine — public interface
|
||||
|
||||
### `_avg_port_bytes() { ... }`
|
||||
15
docs/network/switch/multicast_table.md
Normal file
15
docs/network/switch/multicast_table.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# network/switch/multicast_table.h
|
||||
|
||||
## class MulticastTable — public interface
|
||||
|
||||
### `MulticastTable();`
|
||||
### `bool add_tree(std::size_t group_id, uint16_t tree_id);`
|
||||
### `bool delete_tree(std::size_t group_id, uint16_t tree_id);`
|
||||
### `bool add_child_port(std::size_t group_id, uint16_t tree_id, PortId out_port);`
|
||||
### `bool delete_child_port(std::size_t group_id, uint16_t tree_id, PortId out_port);`
|
||||
### `bool set_parent(std::size_t group_id, uint16_t tree_id, PortId parent);`
|
||||
### `bool set_weight(std::size_t group_id, uint16_t tree_id, uint8_t w);`
|
||||
### `bool set_epoch(std::size_t group_id, uint16_t tree_id, uint8_t epoch);`
|
||||
### `const std::vector<McTree> *trees_of(std::size_t group_id) const;`
|
||||
### `std::size_t group_count() const noexcept;`
|
||||
### `std::vector<PortId> get_port_list(PacketGroups groups) const;`
|
||||
5
docs/network/switch/shared_buffer.md
Normal file
5
docs/network/switch/shared_buffer.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# network/switch/shared_buffer.h
|
||||
|
||||
## class SharedBuffer — public interface
|
||||
|
||||
### `virtual bool drain_one(PortId port) override;`
|
||||
19
docs/network/switch/switch_buffer.md
Normal file
19
docs/network/switch/switch_buffer.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# network/switch/switch_buffer.h
|
||||
|
||||
## class NetworkSwitch — public interface
|
||||
|
||||
### `virtual ~SwitchBuffer() = default;`
|
||||
### `Bytes buffer_size() const noexcept;`
|
||||
### `SwitchBufferType type() const noexcept;`
|
||||
### `uint16_t port_cnt() const noexcept;`
|
||||
### `Bytes port_buffered(PortId p) const noexcept;`
|
||||
### `const std::vector<Bytes> &ports_buffered() const noexcept;`
|
||||
### `uint8_t share_ctrl() const noexcept;`
|
||||
### `uint8_t share_mice() const noexcept;`
|
||||
### `uint8_t share_elephant() const noexcept;`
|
||||
### `void set_share_ctrl(uint8_t pct) noexcept;`
|
||||
### `void set_share_mice(uint8_t pct) noexcept;`
|
||||
### `void set_share_elephant(uint8_t pct) noexcept;`
|
||||
### `const Simulator *simulator() const noexcept;`
|
||||
### `const NetworkSwitch *owner() const noexcept;`
|
||||
### `void set_egress_links(const std::vector<Link*> &links);`
|
||||
8
docs/network/switch/unicast_table.md
Normal file
8
docs/network/switch/unicast_table.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# network/switch/unicast_table.h
|
||||
|
||||
## class UnicastTable — public interface
|
||||
|
||||
### `UnicastTable() = default;`
|
||||
### `std::vector<PortId> get_port_list(NodeId dst_node, PortId dst_port) const;`
|
||||
### `bool add_entry(NodeId dst_node, PortId dst_port, PortId out_port);`
|
||||
### `bool delete_entry(NodeId dst_node, PortId dst_port, PortId out_port);`
|
||||
255
tools/extract_api.py
Executable file
255
tools/extract_api.py
Executable file
@@ -0,0 +1,255 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
extract_api.py
|
||||
Scan C++ headers in ./src for public interfaces (public class/struct methods
|
||||
and free function declarations) and emit markdown snippets mirroring the src/
|
||||
tree into ./docs.
|
||||
|
||||
Each interface line is rendered as:
|
||||
### `signature`
|
||||
|
||||
Usage:
|
||||
cd dofs
|
||||
python3 tools/extract_api.py
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC_DIR = REPO_ROOT / "src"
|
||||
DOCS_DIR = REPO_ROOT / "docs"
|
||||
|
||||
HEADER_EXTS = {".h", ".hh", ".hpp", ".hxx"}
|
||||
|
||||
# ---------- Utilities ----------
|
||||
|
||||
def read_text(p: Path) -> str:
|
||||
try:
|
||||
return p.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception as e:
|
||||
print(f"[WARN] failed reading {p}: {e}", file=sys.stderr)
|
||||
return ""
|
||||
|
||||
def strip_comments(code: str) -> str:
|
||||
# Remove /* ... */ (including multiline) and // ... to end of line
|
||||
no_block = re.sub(r"/\*.*?\*/", "", code, flags=re.S)
|
||||
no_line = re.sub(r"//.*?$", "", no_block, flags=re.M)
|
||||
return no_line
|
||||
|
||||
def collapse_ws(s: str) -> str:
|
||||
return re.sub(r"\s+", " ", s).strip()
|
||||
|
||||
def ensure_dir(path: Path):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# ---------- Heuristic extractors ----------
|
||||
|
||||
ACCESS_RE = re.compile(r"^\s*(public|private|protected)\s*:\s*$")
|
||||
CLASS_START_RE = re.compile(r"^\s*(class|struct)\s+([A-Za-z_]\w*)\b")
|
||||
POSSIBLE_FUNC_DECL_RE = re.compile(
|
||||
r"""^[^;{}()]*\b
|
||||
(?!typedef\b)(?!using\b)(?!friend\b)
|
||||
[A-Za-z_~]\w*\s*
|
||||
\(
|
||||
[^;]* # params
|
||||
\)
|
||||
(?:\s*(?:const|noexcept|override|final|=0|=\s*default|=\s*delete))*\s*
|
||||
;
|
||||
\s*$""",
|
||||
re.X,
|
||||
)
|
||||
POSSIBLE_INLINE_DEF_RE = re.compile(
|
||||
r"""^[^;{}()]*\b
|
||||
(?!typedef\b)(?!using\b)(?!friend\b)
|
||||
[A-Za-z_~]\w*\s*
|
||||
\(
|
||||
[^{;]* # params
|
||||
\)
|
||||
(?:\s*(?:const|noexcept|override|final))*\s*
|
||||
\{""",
|
||||
re.X,
|
||||
)
|
||||
SKIP_PREFIXES = ("#define", "#include", "static_assert", "enum ", "enum class ",
|
||||
"template<", "namespace ", "using ", "typedef ", "friend ",
|
||||
"struct ", "class ")
|
||||
|
||||
def extract_public_methods(lines, is_struct_default_public: bool):
|
||||
public = is_struct_default_public
|
||||
out = []
|
||||
depth = 0
|
||||
for raw in lines:
|
||||
line = raw.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
# Track nested braces to avoid confusing nested scopes
|
||||
depth += raw.count("{")
|
||||
depth -= raw.count("}")
|
||||
|
||||
m = ACCESS_RE.match(line)
|
||||
if m and depth >= 0:
|
||||
public = (m.group(1) == "public")
|
||||
continue
|
||||
|
||||
if not public:
|
||||
continue
|
||||
|
||||
if line.startswith(SKIP_PREFIXES) or line.endswith(":"):
|
||||
continue
|
||||
|
||||
if POSSIBLE_FUNC_DECL_RE.match(line):
|
||||
out.append(collapse_ws(line))
|
||||
continue
|
||||
|
||||
if POSSIBLE_INLINE_DEF_RE.match(line):
|
||||
sig = line.split("{", 1)[0].rstrip()
|
||||
out.append(collapse_ws(sig) + " { ... }")
|
||||
continue
|
||||
|
||||
return out
|
||||
|
||||
def extract_free_function_decls(code: str):
|
||||
# Remove class/struct bodies to avoid capturing methods
|
||||
scrubbed = []
|
||||
toks = code.splitlines()
|
||||
in_class = False
|
||||
brace_balance = 0
|
||||
for line in toks:
|
||||
if not in_class:
|
||||
if CLASS_START_RE.match(line):
|
||||
in_class = True
|
||||
brace_balance = line.count("{") - line.count("}")
|
||||
scrubbed.append("")
|
||||
continue
|
||||
else:
|
||||
brace_balance += line.count("{") - line.count("}")
|
||||
if brace_balance <= 0:
|
||||
in_class = False
|
||||
scrubbed.append("")
|
||||
continue
|
||||
scrubbed.append(line)
|
||||
|
||||
text = "\n".join(scrubbed)
|
||||
|
||||
out = []
|
||||
for raw in text.splitlines():
|
||||
line = raw.strip()
|
||||
if not line or line.startswith(SKIP_PREFIXES):
|
||||
continue
|
||||
if POSSIBLE_FUNC_DECL_RE.match(line):
|
||||
out.append(collapse_ws(line))
|
||||
elif POSSIBLE_INLINE_DEF_RE.match(line):
|
||||
sig = line.split("{", 1)[0].rstrip()
|
||||
out.append(collapse_ws(sig) + " { ... }")
|
||||
return out
|
||||
|
||||
def split_top_level_classes(code: str):
|
||||
lines = code.splitlines()
|
||||
results = []
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
m = CLASS_START_RE.match(lines[i])
|
||||
if not m:
|
||||
i += 1
|
||||
continue
|
||||
kind, name = m.group(1), m.group(2)
|
||||
# Find opening brace on same or subsequent lines
|
||||
j = i
|
||||
if "{" not in lines[j]:
|
||||
j += 1
|
||||
while j < len(lines) and "{" not in lines[j]:
|
||||
j += 1
|
||||
if j >= len(lines):
|
||||
i += 1
|
||||
continue
|
||||
# Capture until matching close
|
||||
depth = 0
|
||||
body = []
|
||||
while j < len(lines):
|
||||
depth += lines[j].count("{")
|
||||
depth -= lines[j].count("}")
|
||||
body.append(lines[j])
|
||||
if depth <= 0 and "}" in lines[j]:
|
||||
break
|
||||
j += 1
|
||||
body_inner = body[1:-1] if body else []
|
||||
results.append((name, kind == "struct", body_inner))
|
||||
i = j + 1
|
||||
return results
|
||||
|
||||
# ---------- Main per-file processing ----------
|
||||
|
||||
def process_header(path: Path):
|
||||
raw = read_text(path)
|
||||
if not raw:
|
||||
return None
|
||||
|
||||
code = strip_comments(raw)
|
||||
|
||||
# Collect classes
|
||||
class_entries = []
|
||||
for cname, is_struct, body in split_top_level_classes(code):
|
||||
methods = extract_public_methods(body, is_struct_default_public=is_struct)
|
||||
if methods:
|
||||
class_entries.append((cname, methods))
|
||||
|
||||
# Collect free function decls
|
||||
free_funcs = extract_free_function_decls(code)
|
||||
|
||||
if not class_entries and not free_funcs:
|
||||
return None
|
||||
|
||||
# Build markdown with ### `signature` items
|
||||
rel = path.relative_to(SRC_DIR)
|
||||
md_lines = []
|
||||
md_lines.append(f"# {rel.as_posix()}")
|
||||
md_lines.append("")
|
||||
|
||||
if free_funcs:
|
||||
md_lines.append("## Free functions")
|
||||
md_lines.append("")
|
||||
for sig in free_funcs:
|
||||
md_lines.append(f"### `{sig}`")
|
||||
md_lines.append("")
|
||||
|
||||
for cname, methods in class_entries:
|
||||
md_lines.append(f"## class {cname} — public interface")
|
||||
md_lines.append("")
|
||||
for sig in methods:
|
||||
md_lines.append(f"### `{sig}`")
|
||||
md_lines.append("")
|
||||
|
||||
return "\n".join(md_lines)
|
||||
|
||||
def write_markdown(src_header: Path, content: str):
|
||||
rel = src_header.relative_to(SRC_DIR)
|
||||
out_path = DOCS_DIR / rel
|
||||
out_path = out_path.with_suffix(".md")
|
||||
ensure_dir(out_path)
|
||||
out_path.write_text(content, encoding="utf-8")
|
||||
return out_path
|
||||
|
||||
def main():
|
||||
if not SRC_DIR.exists():
|
||||
print(f"[ERR] src/ not found at {SRC_DIR}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
generated = 0
|
||||
for path in SRC_DIR.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
if path.suffix.lower() not in HEADER_EXTS:
|
||||
continue
|
||||
result = process_header(path)
|
||||
if result:
|
||||
out = write_markdown(path, result)
|
||||
generated += 1
|
||||
print(f"[OK] {out.relative_to(REPO_ROOT)}")
|
||||
|
||||
if generated == 0:
|
||||
print("[INFO] no public interfaces detected (heuristics may have filtered everything)]")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user