mirror of
https://github.com/valmojr/armatak.git
synced 2026-06-13 14:23:28 +00:00
Compare commits
2 Commits
v1.5.0
...
fuck_arma_
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a5a7a17a3 | |||
| 0486f2a285 |
@@ -78,12 +78,12 @@ class Cfg3den {
|
||||
condition = "objectVehicle";
|
||||
typeName = "STRING";
|
||||
};
|
||||
class armatak_attribute_marker_video_url {
|
||||
displayName = "Video Feed URL";
|
||||
tooltip = "Video feed URL injected into __video on TAK";
|
||||
property = "armatak_attribute_marker_video_url";
|
||||
class armatak_attribute_video_url {
|
||||
displayName = "Video URL (RTSP)";
|
||||
tooltip = "RTSP stream URL for UAS Tool integration. When set, the drone will appear in the ATAK UAS Tool with FOV cone and video feed. Format: rtsp://address:port/path (e.g. rtsp://192.168.1.10:8554/live/drone1). Leave empty to disable UAS Tool integration for this entity.";
|
||||
property = "armatak_attribute_video_url";
|
||||
control = "Edit";
|
||||
expression = "_this setVariable ['armatak_attribute_marker_video_url',_value]";
|
||||
expression = "_this setVariable ['armatak_attribute_video_url',_value]";
|
||||
defaultValue = "''";
|
||||
condition = "objectVehicle";
|
||||
typeName = "STRING";
|
||||
|
||||
@@ -19,6 +19,12 @@ class CfgFunctions {
|
||||
class send_marker_cot {
|
||||
file = "\armatak\armatak\addons\main\functions\api\fn_send_marker_cot.sqf";
|
||||
};
|
||||
class send_uas_video_cot {
|
||||
file = "\armatak\armatak\addons\main\functions\api\fn_send_uas_video_cot.sqf";
|
||||
};
|
||||
class send_uas_sensor_cot {
|
||||
file = "\armatak\armatak\addons\main\functions\api\fn_send_uas_sensor_cot.sqf";
|
||||
};
|
||||
class stop_tcp_socket {
|
||||
file = "\armatak\armatak\addons\main\functions\api\fn_stop_tcp_socket.sqf";
|
||||
};
|
||||
@@ -31,9 +37,6 @@ class CfgFunctions {
|
||||
class extract_marker_callsign {
|
||||
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_marker_callsign.sqf";
|
||||
};
|
||||
class extract_marker_video_url {
|
||||
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_marker_video_url.sqf";
|
||||
};
|
||||
class extract_role {
|
||||
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_role.sqf";
|
||||
};
|
||||
@@ -125,9 +128,6 @@ class CfgFunctions {
|
||||
class convert_to_rut_mandol {
|
||||
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_rut_mandol.sqf";
|
||||
};
|
||||
class convert_to_hellanmaa {
|
||||
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_hellanmaa.sqf";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -32,3 +32,6 @@ if (!isNil "_pre_defined_role") then {
|
||||
};
|
||||
|
||||
_cot = [_drone, _atak_role, _atak_callsign] call armatak_fnc_send_marker_cot;
|
||||
|
||||
[_drone] call armatak_fnc_send_uas_video_cot;
|
||||
[_drone] call armatak_fnc_send_uas_sensor_cot;
|
||||
|
||||
55
addons/main/functions/api/fn_send_uas_sensor_cot.sqf
Normal file
55
addons/main/functions/api/fn_send_uas_sensor_cot.sqf
Normal file
@@ -0,0 +1,55 @@
|
||||
// function name: armatak_fnc_send_uas_sensor_cot
|
||||
// function author: Valmo / ArmaTAK contributors
|
||||
// function description:
|
||||
// Sends a b-m-p-s-p-loc CoT event every router tick (1 s) for a drone.
|
||||
// This is the "sensor position" event consumed by the ATAK UAS Tool to:
|
||||
// - Draw the FOV cone on the moving map.
|
||||
// - Compute four-corners for AR marker overlay on the video feed.
|
||||
// - Show the SPoI (Sensor Point of Interest) crosshair.
|
||||
//
|
||||
// The event references the drone's b-i-v video endpoint via the drone UUID,
|
||||
// so armatak_fnc_send_uas_video_cot must also be called for the same drone.
|
||||
//
|
||||
// Exits silently when "armatak_attribute_video_url" is not set, which keeps
|
||||
// the behavior identical to the old fn_send_drone_cot for drones without a
|
||||
// configured video stream.
|
||||
//
|
||||
// Arguments:
|
||||
// 0: _drone <OBJECT> The drone object.
|
||||
//
|
||||
// Return value: none
|
||||
|
||||
params ["_drone"];
|
||||
|
||||
private _video_url = _drone getVariable ["armatak_attribute_video_url", ""];
|
||||
if (_video_url == "") exitWith {};
|
||||
|
||||
private _uuid = _drone call armatak_fnc_extract_uuid;
|
||||
private _sensor_uid = _uuid + "-sensor";
|
||||
private _callsign = [_drone] call armatak_fnc_extract_marker_callsign;
|
||||
|
||||
private _pos = (getPos _drone) call armatak_client_fnc_convertClientLocation;
|
||||
private _lat = _pos select 0;
|
||||
private _lon = _pos select 1;
|
||||
private _hae = _pos select 2;
|
||||
|
||||
private _azimuth = parseNumber ((getDir _drone) toFixed 0);
|
||||
|
||||
private _allTurrets = [_drone, false] call BIS_fnc_allTurrets;
|
||||
if (count _allTurrets > 0) then {
|
||||
private _firstTurretPath = _allTurrets select 0;
|
||||
private _turretWeapons = _drone weaponsTurret _firstTurretPath;
|
||||
if (_turretWeapons isNotEqualTo []) then {
|
||||
private _tDir = _drone weaponDirection (_turretWeapons select 0);
|
||||
if (!((_tDir select 0) == 0 && (_tDir select 1) == 0)) then {
|
||||
_azimuth = round (((_tDir select 0) atan2 (_tDir select 1) + 360) mod 360);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
private _fov = _drone getVariable ["armatak_uas_fov", 60];
|
||||
|
||||
private _range = round (((getPosATL _drone) select 2) max 1);
|
||||
|
||||
private _payload = [_sensor_uid, _uuid, _callsign, _lat, _lon, _hae, _azimuth, _fov, _range];
|
||||
"armatak" callExtension ["tcp_socket:cot:uas_sensor", [_payload]];
|
||||
30
addons/main/functions/api/fn_send_uas_video_cot.sqf
Normal file
30
addons/main/functions/api/fn_send_uas_video_cot.sqf
Normal file
@@ -0,0 +1,30 @@
|
||||
// function name: armatak_fnc_send_uas_video_cot
|
||||
// function author: Valmo / ArmaTAK contributors
|
||||
// function description:
|
||||
// Sends a b-i-v CoT event that declares the RTSP video endpoint for a drone.
|
||||
// The ATAK UAS Tool picks this up and shows the drone in its UAS list with
|
||||
// the associated video feed available for playback.
|
||||
//
|
||||
// The drone entity MUST have the variable "armatak_attribute_video_url" set
|
||||
// to a valid RTSP URL, e.g.:
|
||||
// _drone setVariable ["armatak_attribute_video_url", "rtsp://192.168.1.10:8554/live/drone1"];
|
||||
// or via the 3DEN attribute "Video URL (RTSP)" in the ARMA Team Awareness Kit
|
||||
// attribute category.
|
||||
//
|
||||
// If the variable is absent or empty the function exits silently.
|
||||
//
|
||||
// Arguments:
|
||||
// 0: _drone <OBJECT> The drone object.
|
||||
//
|
||||
// Return value: none
|
||||
|
||||
params ["_drone"];
|
||||
|
||||
private _video_url = _drone getVariable ["armatak_attribute_video_url", ""];
|
||||
if (_video_url == "") exitWith {};
|
||||
|
||||
private _uuid = _drone call armatak_fnc_extract_uuid;
|
||||
private _callsign = [_drone] call armatak_fnc_extract_marker_callsign;
|
||||
|
||||
private _payload = [_uuid, _callsign, _video_url];
|
||||
"armatak" callExtension ["tcp_socket:cot:uas_video", [_payload]];
|
||||
@@ -5,3 +5,4 @@ pub mod eud;
|
||||
pub mod gps;
|
||||
pub mod message;
|
||||
pub mod nato;
|
||||
pub mod uas;
|
||||
|
||||
243
src/cot/uas.rs
Normal file
243
src/cot/uas.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
// src/cot/uas.rs
|
||||
//
|
||||
// CoT types required for ATAK UAS Tool integration.
|
||||
//
|
||||
// Two event types are needed so that the UAS Tool plugin recognises a drone:
|
||||
//
|
||||
// b-i-v — Video endpoint declaration. Tells the UAS Tool where
|
||||
// to pull the RTSP stream for this drone.
|
||||
//
|
||||
// b-m-p-s-p-loc — Sensor position event. Carries the camera azimuth,
|
||||
// field-of-view, and slant-range that the UAS Tool uses
|
||||
// to draw the FOV cone on the map and to project AR
|
||||
// markers onto the video feed.
|
||||
//
|
||||
// The two events are linked: the b-m-p-s-p-loc detail contains
|
||||
// <__video uid="<drone-uuid>"/>
|
||||
// which references the uid of the b-i-v event, so the UAS Tool knows which
|
||||
// video stream belongs to this sensor.
|
||||
|
||||
use arma_rs::{FromArma, FromArmaError};
|
||||
use chrono::{Duration, SecondsFormat, Utc};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Parse an RTSP URL of the form rtsp://address:port/path
|
||||
/// into its three components.
|
||||
fn parse_rtsp_url(url: &str) -> Option<(String, String, String)> {
|
||||
let without_proto = url.strip_prefix("rtsp://")?;
|
||||
let slash_pos = without_proto.find('/')?;
|
||||
let host_port = &without_proto[..slash_pos];
|
||||
let path = &without_proto[slash_pos..]; // includes the leading '/'
|
||||
let colon_pos = host_port.rfind(':')?;
|
||||
let address = host_port[..colon_pos].to_string();
|
||||
let port = host_port[colon_pos + 1..].to_string();
|
||||
Some((address, port, path.to_string()))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// b-i-v – Video endpoint declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub struct UasVideoCoTPayload {
|
||||
/// The drone's persistent ATAK UUID (same uid used for PPLI / marker CoT).
|
||||
pub uid: String,
|
||||
/// Human-readable label shown in the UAS Tool video list.
|
||||
pub callsign: String,
|
||||
/// Full RTSP URL, e.g. "rtsp://192.168.1.10:8554/live/drone1".
|
||||
pub video_url: String,
|
||||
}
|
||||
|
||||
impl FromArma for UasVideoCoTPayload {
|
||||
fn from_arma(data: String) -> Result<UasVideoCoTPayload, FromArmaError> {
|
||||
let (uid, callsign, video_url) =
|
||||
<(String, String, String)>::from_arma(data)?;
|
||||
Ok(Self {
|
||||
uid,
|
||||
callsign,
|
||||
video_url,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl UasVideoCoTPayload {
|
||||
/// Build the complete XML string for the b-i-v CoT event.
|
||||
/// Returns an empty string if the RTSP URL cannot be parsed.
|
||||
pub fn to_xml(&self) -> String {
|
||||
let (address, port, path) = match parse_rtsp_url(&self.video_url) {
|
||||
Some(parts) => parts,
|
||||
None => {
|
||||
log::warn!(
|
||||
"UasVideoCoTPayload: could not parse RTSP URL: {}",
|
||||
self.video_url
|
||||
);
|
||||
return String::new();
|
||||
}
|
||||
};
|
||||
|
||||
let now =
|
||||
Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||
// Long stale time: the video endpoint is considered valid for 1 hour.
|
||||
// The CoT is re-sent every router tick so it stays fresh even if the
|
||||
// TAK server restarts.
|
||||
let stale = (Utc::now() + Duration::seconds(3600))
|
||||
.to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||
|
||||
let mut xml = String::new();
|
||||
xml.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
|
||||
xml.push_str(&format!(
|
||||
"<event type=\"b-i-v\" version=\"2.0\" how=\"m-g\" \
|
||||
uid=\"{uid}\" time=\"{now}\" start=\"{now}\" stale=\"{stale}\">",
|
||||
uid = self.uid,
|
||||
now = now,
|
||||
stale = stale
|
||||
));
|
||||
// b-i-v events carry no real geographic position.
|
||||
xml.push_str(
|
||||
"<point lat=\"0\" lon=\"0\" hae=\"9999999.0\" \
|
||||
ce=\"9999999.0\" le=\"9999999.0\"/>",
|
||||
);
|
||||
xml.push_str("<detail>");
|
||||
xml.push_str("<__video>");
|
||||
xml.push_str(&format!(
|
||||
"<ConnectionEntry \
|
||||
protocol=\"rtsp\" \
|
||||
path=\"{path}\" \
|
||||
address=\"{address}\" \
|
||||
port=\"{port}\" \
|
||||
uid=\"{uid}\" \
|
||||
alias=\"{callsign}\" \
|
||||
roverPort=\"-1\" \
|
||||
rtspReliable=\"0\" \
|
||||
ignoreEmbeddedKLV=\"False\" \
|
||||
networkTimeout=\"0\" \
|
||||
bufferTime=\"-1\"/>",
|
||||
path = path,
|
||||
address = address,
|
||||
port = port,
|
||||
uid = self.uid,
|
||||
callsign = self.callsign,
|
||||
));
|
||||
xml.push_str("</__video>");
|
||||
xml.push_str(&format!(
|
||||
"<contact callsign=\"{}\"/>",
|
||||
self.callsign
|
||||
));
|
||||
xml.push_str("</detail>");
|
||||
xml.push_str("</event>");
|
||||
xml
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// b-m-p-s-p-loc – Sensor position (FOV cone + video link)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub struct UasSensorCoTPayload {
|
||||
/// UID for this sensor event — conventionally "<drone-uuid>-sensor".
|
||||
pub uid: String,
|
||||
/// The drone's ATAK UUID; must match the uid used in the b-i-v event so
|
||||
/// the UAS Tool can link sensor data to the correct video stream.
|
||||
pub video_uid: String,
|
||||
/// Callsign shown in the UAS Tool sensor list.
|
||||
pub callsign: String,
|
||||
/// Drone latitude in decimal degrees (WGS-84).
|
||||
pub point_lat: f64,
|
||||
/// Drone longitude in decimal degrees (WGS-84).
|
||||
pub point_lon: f64,
|
||||
/// Drone height above ellipsoid in metres (WGS-84).
|
||||
pub point_hae: f32,
|
||||
/// Camera azimuth in degrees, clockwise from true North (0–359).
|
||||
pub azimuth: i32,
|
||||
/// Camera horizontal field of view in degrees.
|
||||
pub fov: i32,
|
||||
/// Estimated slant range from drone to ground point in metres.
|
||||
/// A good approximation is the drone's AGL altitude.
|
||||
pub range: i32,
|
||||
}
|
||||
|
||||
impl FromArma for UasSensorCoTPayload {
|
||||
fn from_arma(data: String) -> Result<UasSensorCoTPayload, FromArmaError> {
|
||||
let (
|
||||
uid,
|
||||
video_uid,
|
||||
callsign,
|
||||
point_lat,
|
||||
point_lon,
|
||||
point_hae,
|
||||
azimuth,
|
||||
fov,
|
||||
range,
|
||||
) = <(String, String, String, f64, f64, f32, i32, i32, i32)>::from_arma(
|
||||
data,
|
||||
)?;
|
||||
Ok(Self {
|
||||
uid,
|
||||
video_uid,
|
||||
callsign,
|
||||
point_lat,
|
||||
point_lon,
|
||||
point_hae,
|
||||
azimuth,
|
||||
fov,
|
||||
range,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl UasSensorCoTPayload {
|
||||
/// Build the complete XML string for the b-m-p-s-p-loc CoT event.
|
||||
pub fn to_xml(&self) -> String {
|
||||
let now =
|
||||
Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||
// 60-second stale: must be refreshed every router tick (1 s) to keep
|
||||
// the FOV cone visible on the map.
|
||||
let stale = (Utc::now() + Duration::seconds(60))
|
||||
.to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||
|
||||
let mut xml = String::new();
|
||||
xml.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
|
||||
xml.push_str(&format!(
|
||||
"<event type=\"b-m-p-s-p-loc\" version=\"2.0\" how=\"h-g-i-g-o\" \
|
||||
uid=\"{uid}\" time=\"{now}\" start=\"{now}\" stale=\"{stale}\">",
|
||||
uid = self.uid,
|
||||
now = now,
|
||||
stale = stale,
|
||||
));
|
||||
xml.push_str(&format!(
|
||||
"<point lat=\"{lat}\" lon=\"{lon}\" hae=\"{hae}\" \
|
||||
ce=\"9999999.0\" le=\"9999999.0\"/>",
|
||||
lat = self.point_lat,
|
||||
lon = self.point_lon,
|
||||
hae = self.point_hae,
|
||||
));
|
||||
xml.push_str("<detail>");
|
||||
// fovAlpha controls the transparency of the FOV cone fill (0–1).
|
||||
// 0.537 ≈ 137/255, the value used by the real UAS Tool.
|
||||
xml.push_str(&format!(
|
||||
"<sensor \
|
||||
fov=\"{fov}\" \
|
||||
fovRed=\"1\" \
|
||||
fovGreen=\"1\" \
|
||||
fovBlue=\"1\" \
|
||||
fovAlpha=\"0.5372549\" \
|
||||
displayMagneticReference=\"0\" \
|
||||
range=\"{range}\" \
|
||||
azimuth=\"{az}\"/>",
|
||||
fov = self.fov,
|
||||
range = self.range,
|
||||
az = self.azimuth,
|
||||
));
|
||||
// Link this sensor event to the b-i-v video endpoint.
|
||||
xml.push_str(&format!("<__video uid=\"{}\"/>", self.video_uid));
|
||||
xml.push_str(&format!(
|
||||
"<contact callsign=\"{}\"/>",
|
||||
self.callsign
|
||||
));
|
||||
xml.push_str("</detail>");
|
||||
xml.push_str("</event>");
|
||||
xml
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,10 @@ pub fn init() -> Extension {
|
||||
.command("eud", tcp::cot::send_eud_cot)
|
||||
.command("marker", tcp::cot::send_marker_cot)
|
||||
.command("digital_pointer", tcp::cot::send_digital_pointer_cot)
|
||||
.command("chat", tcp::cot::send_message_cot),
|
||||
.command("chat", tcp::cot::send_message_cot)
|
||||
// UAS Tool integration
|
||||
.command("uas_video", tcp::cot::send_uas_video_cot)
|
||||
.command("uas_sensor", tcp::cot::send_uas_sensor_cot),
|
||||
)
|
||||
.group(
|
||||
"draw",
|
||||
|
||||
@@ -39,3 +39,32 @@ pub fn send_message_cot(
|
||||
|
||||
"Sending Message CoT to TCP server"
|
||||
}
|
||||
|
||||
/// Send a b-i-v CoT that declares the RTSP video endpoint for a drone.
|
||||
/// Called by SQF via: "armatak" callExtension ["tcp_socket:cot:uas_video", [payload]]
|
||||
///
|
||||
/// Returns early without sending if the RTSP URL in the payload cannot be parsed.
|
||||
pub fn send_uas_video_cot(
|
||||
ctx: Context,
|
||||
payload: cot::uas::UasVideoCoTPayload,
|
||||
) -> &'static str {
|
||||
let xml = payload.to_xml();
|
||||
if !xml.is_empty() {
|
||||
send_payload(ctx, xml);
|
||||
}
|
||||
|
||||
"Sending UAS Video (b-i-v) CoT to TCP server"
|
||||
}
|
||||
|
||||
/// Send a b-m-p-s-p-loc CoT carrying the drone camera's azimuth, FOV, and
|
||||
/// slant-range so the UAS Tool can draw the FOV cone on the map.
|
||||
/// Called by SQF via: "armatak" callExtension ["tcp_socket:cot:uas_sensor", [payload]]
|
||||
pub fn send_uas_sensor_cot(
|
||||
ctx: Context,
|
||||
payload: cot::uas::UasSensorCoTPayload,
|
||||
) -> &'static str {
|
||||
let xml = payload.to_xml();
|
||||
send_payload(ctx, xml);
|
||||
|
||||
"Sending UAS Sensor (b-m-p-s-p-loc) CoT to TCP server"
|
||||
}
|
||||
|
||||
1
vendor/arma-rs-proc/.cargo-ok
vendored
Normal file
1
vendor/arma-rs-proc/.cargo-ok
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"v":1}
|
||||
6
vendor/arma-rs-proc/.cargo_vcs_info.json
vendored
Normal file
6
vendor/arma-rs-proc/.cargo_vcs_info.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "adfc323899e58f20c05ebb37595d5ca4fd09367f"
|
||||
},
|
||||
"path_in_vcs": "arma-rs-proc"
|
||||
}
|
||||
42
vendor/arma-rs-proc/Cargo.toml
vendored
Normal file
42
vendor/arma-rs-proc/Cargo.toml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "arma-rs-proc"
|
||||
version = "1.11.1"
|
||||
authors = ["Brett Mayson"]
|
||||
build = false
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "proc macros for arma-rs"
|
||||
readme = false
|
||||
keywords = ["arma"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/brettmayson/arma-rs"
|
||||
|
||||
[lib]
|
||||
name = "arma_rs_proc"
|
||||
path = "src/lib.rs"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies.proc-macro2]
|
||||
version = "1.0.92"
|
||||
|
||||
[dependencies.quote]
|
||||
version = "1.0.37"
|
||||
|
||||
[dependencies.syn]
|
||||
version = "2.0.90"
|
||||
features = ["full"]
|
||||
17
vendor/arma-rs-proc/Cargo.toml.orig
generated
vendored
Normal file
17
vendor/arma-rs-proc/Cargo.toml.orig
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "arma-rs-proc"
|
||||
description = "proc macros for arma-rs"
|
||||
version = "1.11.1"
|
||||
edition = "2021"
|
||||
authors = ["Brett Mayson"]
|
||||
repository = "https://github.com/brettmayson/arma-rs"
|
||||
license = "MIT"
|
||||
keywords = ["arma"]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.92"
|
||||
quote = "1.0.37"
|
||||
syn = { version = "2.0.90", features = ["full"] }
|
||||
145
vendor/arma-rs-proc/src/derive/attributes.rs
vendored
Normal file
145
vendor/arma-rs-proc/src/derive/attributes.rs
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
use syn::{Error, Result};
|
||||
|
||||
use crate::derive::CombinedErrors;
|
||||
|
||||
pub struct ContainerAttributes {
|
||||
pub transparent: Attribute<bool>,
|
||||
pub default: Attribute<bool>,
|
||||
}
|
||||
|
||||
impl Default for ContainerAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
transparent: Attribute::new(false),
|
||||
default: Attribute::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseAttr for ContainerAttributes {
|
||||
fn parse_attr(&mut self, meta: syn::meta::ParseNestedMeta) -> Result<()> {
|
||||
if meta.path.is_ident("transparent") {
|
||||
return self.transparent.set(&meta, true);
|
||||
}
|
||||
|
||||
if meta.path.is_ident("default") {
|
||||
return self.default.set(&meta, true);
|
||||
}
|
||||
|
||||
Err(meta.error(format!(
|
||||
"unknown arma container attribute `{}`",
|
||||
path_to_string(&meta.path)
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FieldAttributes {
|
||||
pub default: Attribute<bool>,
|
||||
pub from_str: Attribute<bool>,
|
||||
pub to_string: Attribute<bool>,
|
||||
}
|
||||
|
||||
impl Default for FieldAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
default: Attribute::new(false),
|
||||
from_str: Attribute::new(false),
|
||||
to_string: Attribute::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseAttr for FieldAttributes {
|
||||
fn parse_attr(&mut self, meta: syn::meta::ParseNestedMeta) -> Result<()> {
|
||||
if meta.path.is_ident("default") {
|
||||
return self.default.set(&meta, true);
|
||||
}
|
||||
|
||||
if meta.path.is_ident("from_str") {
|
||||
return self.from_str.set(&meta, true);
|
||||
}
|
||||
|
||||
if meta.path.is_ident("to_string") {
|
||||
return self.to_string.set(&meta, true);
|
||||
}
|
||||
|
||||
Err(meta.error(format!(
|
||||
"unknown arma field attribute `{}`",
|
||||
path_to_string(&meta.path)
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ParseAttr {
|
||||
fn parse_attr(&mut self, meta: syn::meta::ParseNestedMeta) -> Result<()>;
|
||||
}
|
||||
|
||||
pub fn parse_attributes<T>(errors: &mut CombinedErrors, attrs: &[syn::Attribute]) -> T
|
||||
where
|
||||
T: ParseAttr + Default + Sized,
|
||||
{
|
||||
attrs.iter().fold(T::default(), |mut attributes, attr| {
|
||||
if !attr.path().is_ident("arma") {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
let result = attr.parse_nested_meta(|meta| {
|
||||
if let Err(err) = attributes.parse_attr(meta) {
|
||||
errors.add(err);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
if let Err(err) = result {
|
||||
errors.add(err);
|
||||
}
|
||||
attributes
|
||||
})
|
||||
}
|
||||
|
||||
pub struct Attribute<T> {
|
||||
value: T,
|
||||
path: Option<syn::Path>,
|
||||
}
|
||||
|
||||
impl<T> Attribute<T> {
|
||||
fn new(default: T) -> Self {
|
||||
Self {
|
||||
value: default,
|
||||
path: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&mut self, meta: &syn::meta::ParseNestedMeta, value: T) -> Result<()> {
|
||||
if self.is_set() {
|
||||
return Err(meta.error(format!(
|
||||
"duplicate arma attribute `{}`",
|
||||
path_to_string(&meta.path)
|
||||
)));
|
||||
}
|
||||
self.value = value;
|
||||
self.path = Some(meta.path.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_set(&self) -> bool {
|
||||
self.path.is_some()
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error(&self, message: &str) -> Error {
|
||||
Error::new_spanned(self.path.as_ref().unwrap(), message)
|
||||
}
|
||||
}
|
||||
|
||||
fn path_to_string(path: &syn::Path) -> String {
|
||||
path.segments
|
||||
.iter()
|
||||
.map(|s| s.ident.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("::")
|
||||
}
|
||||
124
vendor/arma-rs-proc/src/derive/data.rs
vendored
Normal file
124
vendor/arma-rs-proc/src/derive/data.rs
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::ToTokens;
|
||||
use syn::{Error, Result};
|
||||
|
||||
use crate::derive::{
|
||||
attributes::{parse_attributes, ContainerAttributes, FieldAttributes},
|
||||
r#struct, CombinedErrors,
|
||||
};
|
||||
|
||||
pub struct ContainerData {
|
||||
pub attributes: ContainerAttributes,
|
||||
pub ident: syn::Ident,
|
||||
pub generics: syn::Generics,
|
||||
pub data: Data,
|
||||
}
|
||||
|
||||
pub enum Data {
|
||||
Struct(StructData),
|
||||
}
|
||||
|
||||
pub enum StructData {
|
||||
Map(Vec<FieldNamed>),
|
||||
Tuple(Vec<FieldUnnamed>),
|
||||
NewType(FieldUnnamed),
|
||||
}
|
||||
|
||||
impl ContainerData {
|
||||
pub fn from_input(errors: &mut CombinedErrors, input: syn::DeriveInput) -> Result<Self> {
|
||||
let data = match input.data {
|
||||
syn::Data::Struct(data) => Data::Struct(StructData::new(errors, data)?),
|
||||
syn::Data::Enum(_) => Err(Error::new(Span::call_site(), "enums aren't supported"))?,
|
||||
syn::Data::Union(_) => Err(Error::new(Span::call_site(), "unions aren't supported"))?,
|
||||
};
|
||||
let attributes = parse_attributes::<ContainerAttributes>(errors, &input.attrs);
|
||||
|
||||
Ok(Self {
|
||||
attributes,
|
||||
ident: input.ident,
|
||||
generics: input.generics,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate_attributes(&self, errors: &mut CombinedErrors) {
|
||||
match self.data {
|
||||
Data::Struct(ref data) => {
|
||||
r#struct::validate_attributes(errors, &self.attributes, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn impl_into_arma(&self) -> TokenStream {
|
||||
match self.data {
|
||||
Data::Struct(ref data) => r#struct::impl_into_arma(&self.attributes, data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn impl_from_arma(&self) -> TokenStream {
|
||||
match self.data {
|
||||
Data::Struct(ref data) => r#struct::impl_from_arma(&self.attributes, data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FieldNamed {
|
||||
pub attributes: FieldAttributes,
|
||||
pub ident: syn::Ident,
|
||||
pub name: String,
|
||||
pub _ty: syn::Type,
|
||||
}
|
||||
|
||||
pub struct FieldUnnamed {
|
||||
pub attributes: FieldAttributes,
|
||||
pub index: syn::Index,
|
||||
pub ty: syn::Type,
|
||||
}
|
||||
|
||||
impl FieldNamed {
|
||||
pub fn new(errors: &mut CombinedErrors, field: syn::Field) -> Self {
|
||||
let ident = field.ident.unwrap();
|
||||
let name = ident.to_string();
|
||||
Self {
|
||||
attributes: parse_attributes::<FieldAttributes>(errors, &field.attrs),
|
||||
ident,
|
||||
name,
|
||||
_ty: field.ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FieldUnnamed {
|
||||
pub fn new(errors: &mut CombinedErrors, field: syn::Field, index: usize) -> Self {
|
||||
Self {
|
||||
attributes: parse_attributes::<FieldAttributes>(errors, &field.attrs),
|
||||
index: syn::Index::from(index),
|
||||
ty: field.ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Field {
|
||||
fn attributes(&self) -> &FieldAttributes;
|
||||
fn token(&self) -> TokenStream;
|
||||
}
|
||||
|
||||
impl Field for FieldNamed {
|
||||
fn attributes(&self) -> &FieldAttributes {
|
||||
&self.attributes
|
||||
}
|
||||
|
||||
fn token(&self) -> TokenStream {
|
||||
self.ident.to_token_stream()
|
||||
}
|
||||
}
|
||||
|
||||
impl Field for FieldUnnamed {
|
||||
fn attributes(&self) -> &FieldAttributes {
|
||||
&self.attributes
|
||||
}
|
||||
|
||||
fn token(&self) -> TokenStream {
|
||||
self.index.to_token_stream()
|
||||
}
|
||||
}
|
||||
72
vendor/arma-rs-proc/src/derive/mod.rs
vendored
Normal file
72
vendor/arma-rs-proc/src/derive/mod.rs
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
mod attributes;
|
||||
mod data;
|
||||
mod r#struct;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{DeriveInput, Result};
|
||||
|
||||
use data::ContainerData;
|
||||
|
||||
pub fn generate_from_arma(input: DeriveInput) -> Result<TokenStream> {
|
||||
let container = parse_container_data(input)?;
|
||||
let body = container.impl_from_arma();
|
||||
|
||||
let ident = container.ident;
|
||||
let (impl_generics, ty_generics, where_clause) = container.generics.split_for_impl();
|
||||
Ok(quote! {
|
||||
#[automatically_derived]
|
||||
impl #impl_generics arma_rs::FromArma for #ident #ty_generics #where_clause {
|
||||
fn from_arma(func_input: String) -> Result<Self, arma_rs::FromArmaError> {
|
||||
#body
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn generate_into_arma(input: DeriveInput) -> Result<TokenStream> {
|
||||
let container = parse_container_data(input)?;
|
||||
let body = container.impl_into_arma();
|
||||
|
||||
let ident = container.ident;
|
||||
let (impl_generics, ty_generics, where_clause) = container.generics.split_for_impl();
|
||||
Ok(quote! {
|
||||
#[automatically_derived]
|
||||
impl #impl_generics arma_rs::IntoArma for #ident #ty_generics #where_clause {
|
||||
fn to_arma(&self) -> arma_rs::Value {
|
||||
#body
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_container_data(input: DeriveInput) -> Result<ContainerData> {
|
||||
let mut errors = CombinedErrors::new();
|
||||
let container = ContainerData::from_input(&mut errors, input)?;
|
||||
container.validate_attributes(&mut errors);
|
||||
errors.into_result().and(Ok(container))
|
||||
}
|
||||
|
||||
pub struct CombinedErrors {
|
||||
root: Option<syn::Error>,
|
||||
}
|
||||
|
||||
impl CombinedErrors {
|
||||
fn new() -> Self {
|
||||
Self { root: None }
|
||||
}
|
||||
|
||||
pub fn add(&mut self, error: syn::Error) {
|
||||
match &mut self.root {
|
||||
Some(root) => root.combine(error),
|
||||
None => self.root = Some(error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_result(self) -> Result<()> {
|
||||
match self.root {
|
||||
Some(error) => Err(error),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
162
vendor/arma-rs-proc/src/derive/struct/from.rs
vendored
Normal file
162
vendor/arma-rs-proc/src/derive/struct/from.rs
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use crate::derive::{
|
||||
attributes::ContainerAttributes,
|
||||
data::{Field, FieldNamed, FieldUnnamed, StructData},
|
||||
};
|
||||
|
||||
pub fn impl_from_arma(attributes: &ContainerAttributes, data: &StructData) -> TokenStream {
|
||||
// For simplicity sake we assume that theres no conflicts and everything has already been validated
|
||||
match &data {
|
||||
StructData::Map(fields) => map_struct(attributes, fields),
|
||||
StructData::Tuple(fields) => tuple_struct(attributes, fields),
|
||||
StructData::NewType(field) => newtype_struct(attributes, field),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_struct(attributes: &ContainerAttributes, fields: &[FieldNamed]) -> TokenStream {
|
||||
if *attributes.transparent.value() {
|
||||
return newtype_struct(attributes, fields.first().unwrap());
|
||||
}
|
||||
|
||||
let mut setup = TokenStream::new();
|
||||
setup.extend(quote! {
|
||||
let mut input_as_values = std::collections::HashMap::<String, arma_rs::Value>::default();
|
||||
|
||||
let input_pairs: Vec<(String, arma_rs::Value)> = FromArma::from_arma(func_input)?;
|
||||
for (k, v) in input_pairs {
|
||||
if input_as_values.insert(k.clone(), v).is_some() {
|
||||
return Err(arma_rs::FromArmaError::DuplicateField(k));
|
||||
}
|
||||
}
|
||||
});
|
||||
if *attributes.default.value() {
|
||||
setup.extend(quote! {
|
||||
let container_default: Self = std::default::Default::default();
|
||||
});
|
||||
};
|
||||
|
||||
let field_bodies = fields.iter().map(|field| {
|
||||
let (ident, name) = (&field.ident, &field.name);
|
||||
|
||||
let some_match = if *field.attributes.from_str.value() {
|
||||
quote!(input_value
|
||||
.to_string()
|
||||
.parse()
|
||||
.map_err(arma_rs::FromArmaError::custom)?)
|
||||
} else {
|
||||
quote!(arma_rs::FromArma::from_arma(input_value.to_string())?)
|
||||
};
|
||||
|
||||
let none_match = if *field.attributes.default.value() {
|
||||
quote!(std::default::Default::default())
|
||||
} else if *attributes.default.value() {
|
||||
quote!(container_default.#ident)
|
||||
} else {
|
||||
quote!(return Err(arma_rs::FromArmaError::MissingField(#name.to_string())))
|
||||
};
|
||||
|
||||
quote! {
|
||||
#ident: match input_as_values.remove(#name) {
|
||||
Some(input_value) => #some_match,
|
||||
None => #none_match,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let check_unknown = quote! {
|
||||
if let Some(unknown) = input_as_values.keys().next() {
|
||||
return Err(arma_rs::FromArmaError::UnknownField(unknown.clone()));
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
#setup
|
||||
let result = Self {
|
||||
#(#field_bodies),*
|
||||
};
|
||||
|
||||
#check_unknown
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn tuple_struct(attributes: &ContainerAttributes, fields: &[FieldUnnamed]) -> TokenStream {
|
||||
let mut setup = TokenStream::new();
|
||||
setup.extend(quote! {
|
||||
let input_as_values: Vec<arma_rs::Value> = arma_rs::FromArma::from_arma(func_input)?;
|
||||
let mut input_as_values = input_as_values.into_iter();
|
||||
});
|
||||
if *attributes.default.value() {
|
||||
setup.extend(quote! {
|
||||
let container_default: Self = std::default::Default::default();
|
||||
});
|
||||
};
|
||||
|
||||
let expected_len = fields.len();
|
||||
let field_bodies = fields.iter().map(|field| {
|
||||
let index = &field.index;
|
||||
|
||||
let some_match = if *field.attributes.from_str.value() {
|
||||
quote!(input_value
|
||||
.to_string()
|
||||
.parse()
|
||||
.map_err(arma_rs::FromArmaError::custom)?)
|
||||
} else {
|
||||
quote!(arma_rs::FromArma::from_arma(input_value.to_string())?)
|
||||
};
|
||||
|
||||
let none_match = if *field.attributes.default.value() {
|
||||
quote!(std::default::Default::default())
|
||||
} else if *attributes.default.value() {
|
||||
quote!(container_default.#index)
|
||||
} else {
|
||||
quote!(return Err(arma_rs::FromArmaError::InvalidLength {
|
||||
expected: #expected_len,
|
||||
actual: #index,
|
||||
}))
|
||||
};
|
||||
|
||||
quote! {
|
||||
match input_as_values.next() {
|
||||
Some(input_value) => #some_match,
|
||||
None => #none_match,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let check_unknown = quote! {
|
||||
let remaining = input_as_values.len();
|
||||
if remaining > 0 {
|
||||
return Err(arma_rs::FromArmaError::InvalidLength {
|
||||
expected: #expected_len,
|
||||
actual: #expected_len + remaining,
|
||||
});
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
#setup
|
||||
let result = Self (
|
||||
#(#field_bodies),*
|
||||
);
|
||||
|
||||
#check_unknown
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn newtype_struct(_attributes: &ContainerAttributes, field: &impl Field) -> TokenStream {
|
||||
let token = field.token();
|
||||
|
||||
let field_body = if *field.attributes().from_str.value() {
|
||||
quote!(func_input.parse().map_err(arma_rs::FromArmaError::custom)?)
|
||||
} else {
|
||||
quote!(arma_rs::FromArma::from_arma(func_input)?)
|
||||
};
|
||||
|
||||
quote! {
|
||||
Ok(Self {
|
||||
#token: #field_body
|
||||
})
|
||||
}
|
||||
}
|
||||
72
vendor/arma-rs-proc/src/derive/struct/into.rs
vendored
Normal file
72
vendor/arma-rs-proc/src/derive/struct/into.rs
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use crate::derive::{
|
||||
attributes::ContainerAttributes,
|
||||
data::{Field, FieldNamed, FieldUnnamed, StructData},
|
||||
};
|
||||
|
||||
pub fn impl_into_arma(attributes: &ContainerAttributes, data: &StructData) -> TokenStream {
|
||||
// For simplicity sake we assume that theres no conflicts and everything has already been validated
|
||||
match &data {
|
||||
StructData::Map(fields) => map_struct(attributes, fields),
|
||||
StructData::Tuple(fields) => tuple_struct(attributes, fields),
|
||||
StructData::NewType(field) => newtype_struct(attributes, field),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_struct(attributes: &ContainerAttributes, fields: &[FieldNamed]) -> TokenStream {
|
||||
if *attributes.transparent.value() {
|
||||
return newtype_struct(attributes, fields.first().unwrap());
|
||||
}
|
||||
|
||||
let field_bodies = fields.iter().map(|field| {
|
||||
let (ident, name) = (&field.ident, &field.name);
|
||||
|
||||
let (key, value) = if *field.attributes.to_string.value() {
|
||||
(quote!(#name.to_string()), quote!(self.#ident.to_string()))
|
||||
} else {
|
||||
(quote!(#name.to_string()), quote!(self.#ident))
|
||||
};
|
||||
|
||||
quote!((#key, arma_rs::IntoArma::to_arma(&#value)))
|
||||
});
|
||||
|
||||
quote! {
|
||||
std::collections::HashMap::<String, arma_rs::Value>::from([
|
||||
#(#field_bodies),*
|
||||
]).to_arma()
|
||||
}
|
||||
}
|
||||
|
||||
fn tuple_struct(_attributes: &ContainerAttributes, fields: &[FieldUnnamed]) -> TokenStream {
|
||||
let field_bodies = fields.iter().map(|field| {
|
||||
let index = &field.index;
|
||||
|
||||
if *field.attributes.to_string.value() {
|
||||
quote!(self.#index.to_string())
|
||||
} else {
|
||||
quote!(self.#index)
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
Vec::<arma_rs::Value>::from([
|
||||
#(arma_rs::IntoArma::to_arma(&#field_bodies)),*
|
||||
]).to_arma()
|
||||
}
|
||||
}
|
||||
|
||||
fn newtype_struct(_attributes: &ContainerAttributes, field: &impl Field) -> TokenStream {
|
||||
let token = field.token();
|
||||
|
||||
let field_body = if *field.attributes().to_string.value() {
|
||||
quote!(self.#token.to_string())
|
||||
} else {
|
||||
quote!(self.#token)
|
||||
};
|
||||
|
||||
quote! {
|
||||
arma_rs::IntoArma::to_arma(&#field_body)
|
||||
}
|
||||
}
|
||||
62
vendor/arma-rs-proc/src/derive/struct/mod.rs
vendored
Normal file
62
vendor/arma-rs-proc/src/derive/struct/mod.rs
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
mod from;
|
||||
mod into;
|
||||
mod validate;
|
||||
|
||||
use proc_macro2::Span;
|
||||
use syn::{Error, Result};
|
||||
|
||||
pub use from::impl_from_arma;
|
||||
pub use into::impl_into_arma;
|
||||
pub use validate::validate_attributes;
|
||||
|
||||
use crate::derive::{
|
||||
data::{FieldNamed, FieldUnnamed, StructData},
|
||||
CombinedErrors,
|
||||
};
|
||||
|
||||
impl StructData {
|
||||
pub fn new(errors: &mut CombinedErrors, data: syn::DataStruct) -> Result<Self> {
|
||||
match data.fields {
|
||||
syn::Fields::Unit => Err(Error::new(
|
||||
Span::call_site(),
|
||||
"unit-like structs aren't supported",
|
||||
)),
|
||||
syn::Fields::Named(fields) => {
|
||||
if fields.named.is_empty() {
|
||||
return Err(Error::new(
|
||||
Span::call_site(),
|
||||
"unit-like structs aren't supported",
|
||||
));
|
||||
}
|
||||
|
||||
let fields = fields
|
||||
.named
|
||||
.into_iter()
|
||||
.map(|f| FieldNamed::new(errors, f))
|
||||
.collect::<_>();
|
||||
Ok(Self::Map(fields))
|
||||
}
|
||||
syn::Fields::Unnamed(fields) => {
|
||||
if fields.unnamed.is_empty() {
|
||||
return Err(Error::new(
|
||||
Span::call_site(),
|
||||
"unit-like structs aren't supported",
|
||||
));
|
||||
}
|
||||
|
||||
if fields.unnamed.len() == 1 {
|
||||
let field = FieldUnnamed::new(errors, fields.unnamed[0].clone(), 0);
|
||||
Ok(Self::NewType(field))
|
||||
} else {
|
||||
let fields = fields
|
||||
.unnamed
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, f)| FieldUnnamed::new(errors, f, i))
|
||||
.collect::<_>();
|
||||
Ok(Self::Tuple(fields))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
vendor/arma-rs-proc/src/derive/struct/validate.rs
vendored
Normal file
89
vendor/arma-rs-proc/src/derive/struct/validate.rs
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
use syn::Error;
|
||||
|
||||
use crate::derive::{
|
||||
attributes::{Attribute, ContainerAttributes, FieldAttributes},
|
||||
data::StructData,
|
||||
CombinedErrors,
|
||||
};
|
||||
|
||||
pub fn validate_attributes(
|
||||
errors: &mut CombinedErrors,
|
||||
attributes: &ContainerAttributes,
|
||||
data: &StructData,
|
||||
) {
|
||||
if *attributes.transparent.value() {
|
||||
match data {
|
||||
StructData::Map(fields) if fields.len() > 1 => {
|
||||
errors.add(
|
||||
attributes
|
||||
.transparent
|
||||
.error("#[arma(transparent)] structs must have exactly one field"),
|
||||
);
|
||||
}
|
||||
StructData::Tuple(_) => {
|
||||
errors.add(
|
||||
attributes
|
||||
.transparent
|
||||
.error("#[arma(transparent)] cannot be used on tuple like structs"),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(attr) = get_default_attr(attributes, data) {
|
||||
match data {
|
||||
StructData::Map(_) if *attributes.transparent.value() => {
|
||||
errors.add(
|
||||
attr.error("#[arma(default)] and #[arma(transparent)] cannot be used together"),
|
||||
);
|
||||
}
|
||||
StructData::NewType(_) => {
|
||||
errors.add(attr.error("#[arma(default)] cannot be used on new type structs"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let StructData::Tuple(fields) = data {
|
||||
let mut index_first_default = None;
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
match index_first_default {
|
||||
None => {
|
||||
if field.attributes.default.is_set() {
|
||||
index_first_default = Some(index);
|
||||
}
|
||||
}
|
||||
Some(index) => {
|
||||
if !field.attributes.default.is_set() {
|
||||
errors.add(Error::new_spanned(&field.ty,
|
||||
format!("field must have #[arma(default)] because previous field {} has #[arma(default)]", index)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_default_attr<'a>(
|
||||
attributes: &'a ContainerAttributes,
|
||||
data: &'a StructData,
|
||||
) -> Option<&'a Attribute<bool>> {
|
||||
if *attributes.default.value() {
|
||||
return Some(&attributes.default);
|
||||
}
|
||||
|
||||
field_attributes(data)
|
||||
.iter()
|
||||
.find(|attr| *attr.default.value())
|
||||
.map(|f| &f.default)
|
||||
}
|
||||
|
||||
fn field_attributes(data: &StructData) -> Vec<&FieldAttributes> {
|
||||
match data {
|
||||
StructData::Map(fields) => fields.iter().map(|f| &f.attributes).collect(),
|
||||
StructData::Tuple(fields) => fields.iter().map(|f| &f.attributes).collect(),
|
||||
StructData::NewType(field) => vec![&field.attributes],
|
||||
}
|
||||
}
|
||||
159
vendor/arma-rs-proc/src/lib.rs
vendored
Normal file
159
vendor/arma-rs-proc/src/lib.rs
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
mod derive;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::quote;
|
||||
use syn::{DeriveInput, Error, ItemFn};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
/// Used to generate the necessary boilerplate for an Arma extension.
|
||||
/// It should be applied to a function that takes no arguments and returns an extension.
|
||||
pub fn arma(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let ast = syn::parse_macro_input!(item as ItemFn);
|
||||
let init = ast.sig.ident.clone();
|
||||
|
||||
let extern_type = if cfg!(windows) { "stdcall" } else { "C" };
|
||||
|
||||
let ext_init = quote! {
|
||||
if RV_EXTENSION.is_none() {
|
||||
RV_EXTENSION = Some(#init());
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(target_os = "windows", target_arch = "x86"))]
|
||||
let prefix = "safe32_";
|
||||
|
||||
#[cfg(not(all(target_os = "windows", target_arch = "x86")))]
|
||||
let prefix = "";
|
||||
|
||||
macro_rules! fn_ident {
|
||||
( $name:literal ) => {
|
||||
Ident::new(&format!("{prefix}{}", $name), Span::call_site())
|
||||
};
|
||||
}
|
||||
let versionfn = fn_ident!("RVExtensionVersion");
|
||||
let noargfn = fn_ident!("RVExtension");
|
||||
let argfn = fn_ident!("RVExtensionArgs");
|
||||
let callbackfn = fn_ident!("RVExtensionRegisterCallback");
|
||||
let contextfn = fn_ident!("RVExtensionContext");
|
||||
|
||||
TokenStream::from(quote! {
|
||||
use arma_rs::libc as arma_rs_libc;
|
||||
|
||||
static mut RV_EXTENSION: Option<Extension> = None;
|
||||
|
||||
#[cfg(all(target_os="windows", target_arch="x86"))]
|
||||
arma_rs::link_args::windows! {
|
||||
unsafe {
|
||||
raw("/EXPORT:_RVExtensionVersion@8=_safe32_RVExtensionVersion@8");
|
||||
raw("/EXPORT:_RVExtension@12=_safe32_RVExtension@12");
|
||||
raw("/EXPORT:_RVExtensionArgs@20=_safe32_RVExtensionArgs@20");
|
||||
raw("/EXPORT:_RVExtensionRegisterCallback@4=_safe32_RVExtensionRegisterCallback@4");
|
||||
raw("/EXPORT:_RVExtensionContext@8=_safe32_RVExtensionContext@8");
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns extension version, called by Arma on extension load.
|
||||
/// This function is generated by the [`arma_rs::arma`] proc macro.
|
||||
#[no_mangle]
|
||||
#[doc(hidden)]
|
||||
pub unsafe extern #extern_type fn #versionfn(output: *mut arma_rs_libc::c_char, size: arma_rs_libc::size_t) -> arma_rs_libc::c_int {
|
||||
#ext_init
|
||||
if let Some(ext) = &RV_EXTENSION {
|
||||
arma_rs::write_cstr(ext.version().to_string(), output, size);
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
/// Run extension function, called by Arma on `callExtension` without arguments.
|
||||
/// This function is generated by the [`arma_rs::arma`] proc macro.
|
||||
#[no_mangle]
|
||||
#[doc(hidden)]
|
||||
pub unsafe extern #extern_type fn #noargfn(output: *mut arma_rs_libc::c_char, size: arma_rs_libc::size_t, function: *mut arma_rs_libc::c_char) {
|
||||
#ext_init
|
||||
if let Some(ext) = &RV_EXTENSION {
|
||||
if ext.allow_no_args() {
|
||||
ext.handle_call(function, output, size, None, None, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run extension function with arguments, called by Arma on `callExtension` with arguments.
|
||||
/// This function is generated by the [`arma_rs::arma`] proc macro.
|
||||
#[no_mangle]
|
||||
#[doc(hidden)]
|
||||
pub unsafe extern #extern_type fn #argfn(output: *mut arma_rs_libc::c_char, size: arma_rs_libc::size_t, function: *mut arma_rs_libc::c_char, args: *mut *mut arma_rs_libc::c_char, arg_count: arma_rs_libc::c_int) -> arma_rs_libc::c_int {
|
||||
#ext_init
|
||||
if let Some(ext) = &RV_EXTENSION {
|
||||
ext.handle_call(function, output, size, Some(args), Some(arg_count), true)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Set extension callback, called by Arma on extension load.
|
||||
/// This function is generated by the [`arma_rs::arma`] proc macro.
|
||||
#[no_mangle]
|
||||
#[doc(hidden)]
|
||||
pub unsafe extern #extern_type fn #callbackfn(callback: arma_rs::Callback) {
|
||||
#ext_init
|
||||
if let Some(ext) = &mut RV_EXTENSION {
|
||||
ext.register_callback(callback);
|
||||
ext.run_callbacks();
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide extension call context, called by Arma on `callExtension`.
|
||||
/// This function is generated by the [`arma_rs::arma`] proc macro.
|
||||
#[no_mangle]
|
||||
#[doc(hidden)]
|
||||
pub unsafe extern #extern_type fn #contextfn(args: *mut *mut arma_rs_libc::c_char, arg_count: arma_rs_libc::c_int) {
|
||||
#ext_init
|
||||
if let Some(ext) = &mut RV_EXTENSION {
|
||||
ext.handle_call_context(args, arg_count);
|
||||
}
|
||||
}
|
||||
|
||||
#ast
|
||||
})
|
||||
}
|
||||
|
||||
/// Derive implementation of `FromArma`, only supports structs.
|
||||
/// - Map structs are converted from an hashmap.
|
||||
/// - Tuple structs are converted from an array.
|
||||
/// - Newtype structs directly use's the value's `FromArma` implementation.
|
||||
/// - Unit-like structs are not supported.
|
||||
///
|
||||
/// ### Container Attributes
|
||||
/// - `#[arma(transparent)]`: treat single field map structs as if its a newtype structs.
|
||||
/// - `#[arma(default)]`: any missing field will be filled by the structs `Default` implementation.
|
||||
///
|
||||
/// ### Field Attributes
|
||||
/// - `#[arma(from_str)]`: use the types `std::str::FromStr` instead of `FromArma`.
|
||||
/// - `#[arma(default)]`: if missing use its `Default` implementation (takes precedence over container).
|
||||
#[proc_macro_derive(FromArma, attributes(arma))]
|
||||
pub fn derive_from_arma(item: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as DeriveInput);
|
||||
derive::generate_from_arma(input)
|
||||
.unwrap_or_else(Error::into_compile_error)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Derive implementation of `IntoArma`, only supports structs.
|
||||
/// - Map structs are converted to an hashmap.
|
||||
/// - Tuple structs are converted to an array.
|
||||
/// - Newtype structs directly use's the value's `IntoArma` implementation.
|
||||
/// - Unit-like structs are not supported.
|
||||
///
|
||||
/// ### Container Attributes
|
||||
/// - `#[arma(transparent)]`: treat single field map structs as if its a newtype structs.
|
||||
///
|
||||
/// ### Field Attributes
|
||||
/// - `#[arma(to_string)]`: use the types `std::string::ToString` instead of `IntoArma`.
|
||||
#[proc_macro_derive(IntoArma, attributes(arma))]
|
||||
pub fn derive_into_arma(item: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as DeriveInput);
|
||||
derive::generate_into_arma(input)
|
||||
.unwrap_or_else(Error::into_compile_error)
|
||||
.into()
|
||||
}
|
||||
1
vendor/arma-rs/.cargo-ok
vendored
Normal file
1
vendor/arma-rs/.cargo-ok
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"v":1}
|
||||
6
vendor/arma-rs/.cargo_vcs_info.json
vendored
Normal file
6
vendor/arma-rs/.cargo_vcs_info.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "6cac89d7a9d02027bb85a6fa583b3ef0e8bf0f5a"
|
||||
},
|
||||
"path_in_vcs": "arma-rs"
|
||||
}
|
||||
951
vendor/arma-rs/Cargo.lock
generated
vendored
Normal file
951
vendor/arma-rs/Cargo.lock
generated
vendored
Normal file
@@ -0,0 +1,951 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arma-rs"
|
||||
version = "1.11.14"
|
||||
dependencies = [
|
||||
"arma-rs-proc",
|
||||
"chrono",
|
||||
"crossbeam-channel",
|
||||
"libc",
|
||||
"link_args",
|
||||
"log",
|
||||
"seq-macro",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"state",
|
||||
"trybuild",
|
||||
"uuid",
|
||||
"winapi",
|
||||
"windows 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arma-rs-proc"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf67c0d0c7a59275e5ac4f3fce0cbdbcf3ba12e47bc30be6a3327d6a1bc151f8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"log",
|
||||
"rustversion",
|
||||
"windows 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
|
||||
[[package]]
|
||||
name = "link_args"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c7721e472624c9aaad27a5eb6b7c9c6045c7a396f2efb6dabaec1b640d5e89b"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"generator",
|
||||
"scoped-tls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "seq-macro"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
|
||||
[[package]]
|
||||
name = "state"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8"
|
||||
dependencies = [
|
||||
"loom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-triple"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790"
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trybuild"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ae08be68c056db96f0e6c6dd820727cca756ced9e1f4cc7fdd20e2a55e23898"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"target-triple",
|
||||
"termcolor",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.61.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core",
|
||||
"windows-future",
|
||||
"windows-link",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
108
vendor/arma-rs/Cargo.toml
vendored
Normal file
108
vendor/arma-rs/Cargo.toml
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "arma-rs"
|
||||
version = "1.11.14"
|
||||
authors = ["Brett Mayson"]
|
||||
build = "build.rs"
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Arma 3 Extensions in Rust"
|
||||
readme = "README.md"
|
||||
keywords = ["arma"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/brettmayson/arma-rs"
|
||||
|
||||
[features]
|
||||
default = ["extension"]
|
||||
extension = [
|
||||
"libc",
|
||||
"crossbeam-channel",
|
||||
]
|
||||
|
||||
[lib]
|
||||
name = "arma_rs"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
path = "examples/hello_world.rs"
|
||||
|
||||
[[test]]
|
||||
name = "derive"
|
||||
path = "tests/derive.rs"
|
||||
|
||||
[[test]]
|
||||
name = "emulate"
|
||||
path = "tests/emulate.rs"
|
||||
|
||||
[[test]]
|
||||
name = "main"
|
||||
path = "tests/main.rs"
|
||||
|
||||
[dependencies.arma-rs-proc]
|
||||
version = "1.11.1"
|
||||
|
||||
[dependencies.chrono]
|
||||
version = "0.4.40"
|
||||
optional = true
|
||||
|
||||
[dependencies.crossbeam-channel]
|
||||
version = "0.5.14"
|
||||
optional = true
|
||||
|
||||
[dependencies.libc]
|
||||
version = "0.2.171"
|
||||
optional = true
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4.27"
|
||||
|
||||
[dependencies.seq-macro]
|
||||
version = "0.3.6"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.219"
|
||||
features = ["derive"]
|
||||
optional = true
|
||||
|
||||
[dependencies.serde_json]
|
||||
version = "1.0.140"
|
||||
optional = true
|
||||
|
||||
[dependencies.state]
|
||||
version = "0.6.0"
|
||||
|
||||
[dependencies.uuid]
|
||||
version = "1.16.0"
|
||||
optional = true
|
||||
|
||||
[dev-dependencies.trybuild]
|
||||
version = "1.0.104"
|
||||
|
||||
[target.'cfg(all(target_os="windows", target_arch="x86"))'.dependencies.link_args]
|
||||
version = "0.6.0"
|
||||
|
||||
[target."cfg(windows)".dependencies.winapi]
|
||||
version = "0.3.9"
|
||||
features = ["libloaderapi"]
|
||||
|
||||
[target."cfg(windows)".dependencies.windows]
|
||||
version = "0.61.1"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_Console",
|
||||
]
|
||||
41
vendor/arma-rs/Cargo.toml.orig
generated
vendored
Normal file
41
vendor/arma-rs/Cargo.toml.orig
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "arma-rs"
|
||||
description = "Arma 3 Extensions in Rust"
|
||||
version = "1.11.14"
|
||||
edition = "2021"
|
||||
authors = ["Brett Mayson"]
|
||||
repository = "https://github.com/brettmayson/arma-rs"
|
||||
license = "MIT"
|
||||
keywords = ["arma"]
|
||||
readme = "../README.md"
|
||||
|
||||
[dependencies]
|
||||
arma-rs-proc = { path = "../arma-rs-proc", version = "1.11.1" }
|
||||
log = "0.4.27"
|
||||
state = "0.6.0"
|
||||
seq-macro = "0.3.6"
|
||||
|
||||
chrono = { version = "0.4.40", optional = true }
|
||||
crossbeam-channel = { version = "0.5.14", optional = true }
|
||||
libc = { version = "0.2.171", optional = true }
|
||||
serde = { version = "1.0.219", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1.0.140", optional = true }
|
||||
uuid = { version = "1.16.0", optional = true }
|
||||
|
||||
[target.'cfg(all(target_os="windows", target_arch="x86"))'.dependencies]
|
||||
link_args = "0.6.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies.winapi]
|
||||
version = "0.3.9"
|
||||
features = ["libloaderapi"]
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
version = "0.61.1"
|
||||
features = ["Win32_Foundation", "Win32_System_Console"]
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0.104"
|
||||
|
||||
[features]
|
||||
default = ["extension"]
|
||||
extension = ["libc", "crossbeam-channel"]
|
||||
439
vendor/arma-rs/README.md
vendored
Normal file
439
vendor/arma-rs/README.md
vendored
Normal file
@@ -0,0 +1,439 @@
|
||||
# arma-rs
|
||||
|
||||
[Join the arma-rs Discord!](https://discord.gg/qXWUrrwy5d)
|
||||
[](https://codecov.io/gh/BrettMayson/arma-rs)
|
||||
|
||||
The best way to make Arma 3 Extensions.
|
||||
|
||||
## Usage
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
arma-rs = "1.11.10"
|
||||
|
||||
[lib]
|
||||
name = "my_extension"
|
||||
crate-type = ["cdylib"]
|
||||
```
|
||||
|
||||
### Hello World
|
||||
|
||||
```rust
|
||||
use arma_rs::{arma, Extension};
|
||||
|
||||
#[arma]
|
||||
fn init() -> Extension {
|
||||
Extension::build()
|
||||
.command("hello", hello)
|
||||
.command("welcome", welcome)
|
||||
.finish()
|
||||
}
|
||||
|
||||
pub fn hello() -> &'static str {
|
||||
"Hello"
|
||||
}
|
||||
|
||||
pub fn welcome(name: String) -> String {
|
||||
format!("Welcome {}", name)
|
||||
}
|
||||
```
|
||||
|
||||
```sqf
|
||||
"my_extension" callExtension ["hello", []]; // Returns ["Hello", 0, 0]
|
||||
"my_extension" callExtension ["welcome", ["John"]]; // Returns ["Welcome John", 0, 0]
|
||||
```
|
||||
|
||||
## Command Groups
|
||||
|
||||
Commands can be grouped together, making your large projects much easier to manage.
|
||||
|
||||
```rust
|
||||
use arma_rs::{arma, Extension, Group};
|
||||
|
||||
#[arma]
|
||||
fn init() -> Extension {
|
||||
Extension::build()
|
||||
.group("hello",
|
||||
Group::new()
|
||||
.command("english", hello::english)
|
||||
.group("english",
|
||||
Group::new()
|
||||
.command("casual", hello::english_casual)
|
||||
)
|
||||
.command("french", hello::french),
|
||||
)
|
||||
.group("welcome",
|
||||
Group::new()
|
||||
.command("english", welcome::english)
|
||||
.command("french", welcome::french),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
|
||||
mod hello {
|
||||
pub fn english() -> &'static str {
|
||||
"Hello"
|
||||
}
|
||||
pub fn english_casual() -> &'static str {
|
||||
"Hey"
|
||||
}
|
||||
pub fn french() -> &'static str {
|
||||
"Bonjour"
|
||||
}
|
||||
}
|
||||
|
||||
mod welcome {
|
||||
pub fn english(name: String) -> String {
|
||||
format!("Welcome {}", name)
|
||||
}
|
||||
pub fn french(name: String) -> String {
|
||||
format!("Bienvenue {}", name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Commands groups are called by using the format `group:command`. You can nest groups as much as you want.
|
||||
|
||||
```sqf
|
||||
"my_extension" callExtension ["hello:english", []]; // Returns ["Hello", 0, 0]
|
||||
"my_extension" callExtension ["hello:english:casual", []]; // Returns ["Hey", 0, 0]
|
||||
"my_extension" callExtension ["hello:french", []]; // Returns ["Bonjour", 0, 0]
|
||||
```
|
||||
|
||||
## Callbacks
|
||||
|
||||
Extension callbacks can be invoked anywhere in the extension by adding a variable of type `Context` to the start of a handler.
|
||||
|
||||
```rust
|
||||
use arma_rs::Context;
|
||||
|
||||
pub fn sleep(ctx: Context, duration: u64, id: String) {
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(std::time::Duration::from_secs(duration));
|
||||
ctx.callback_data("example_timer", "done", Some(id));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn group() -> arma_rs::Group {
|
||||
arma_rs::Group::new().command("sleep", sleep)
|
||||
}
|
||||
```
|
||||
|
||||
## Call Context
|
||||
|
||||
Since Arma v2.11 additional context is provided each time the extension is called. This context can be accessed through the optional `ArmaCallContext` argument.
|
||||
|
||||
Since Arma v2.18 the context is only requested from Arma when the functionh has `ArmaCallContext` as an argument.
|
||||
|
||||
```rust
|
||||
use arma_rs::{CallContext, CallContextStackTrace};
|
||||
|
||||
pub fn call_context(call_context: CallContext) -> String {
|
||||
format!(
|
||||
"{:?},{:?},{:?},{:?},{:?}",
|
||||
call_context.caller(),
|
||||
call_context.source(),
|
||||
call_context.mission(),
|
||||
call_context.server(),
|
||||
call_context.remote_exec_owner(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn stack_trace(call_context: CallContextStackTrace) -> String {
|
||||
format!(
|
||||
"{:?}\n{:?}",
|
||||
call_context.source(),
|
||||
call_context.stack_trace()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn group() -> arma_rs::Group {
|
||||
arma_rs::Group::new()
|
||||
.command("call_context", call_context)
|
||||
.command("stack_trace", stack_trace)
|
||||
}
|
||||
```
|
||||
|
||||
## Persistent State
|
||||
|
||||
Both the extension and command groups allow for type based persistent state values with at most one instance per type. These state values can then be accessed through the optional `Context` argument.
|
||||
|
||||
### Global State
|
||||
|
||||
Extension state is accessible from any command handler.
|
||||
|
||||
```rust
|
||||
use arma_rs::{arma, Context, ContextState, Extension};
|
||||
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
#[arma]
|
||||
fn init() -> Extension {
|
||||
Extension::build()
|
||||
.command("counter_increment", increment)
|
||||
.state(AtomicU32::new(0))
|
||||
.finish()
|
||||
}
|
||||
|
||||
pub fn increment(ctx: Context) -> Result<(), ()> {
|
||||
let Some(counter) = ctx.global().get::<AtomicU32>() else {
|
||||
return Err(());
|
||||
};
|
||||
counter.fetch_add(1, Ordering::SeqCst);
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Group State
|
||||
|
||||
Command group state is only accessible from command handlers within the same group.
|
||||
|
||||
```rust
|
||||
use arma_rs::{Context, ContextState, Extension};
|
||||
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
pub fn increment(ctx: Context) -> Result<(), ()> {
|
||||
let Some(counter) = ctx.group().get::<AtomicU32>() else {
|
||||
return Err(());
|
||||
};
|
||||
counter.fetch_add(1, Ordering::SeqCst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn group() -> arma_rs::Group {
|
||||
arma_rs::Group::new()
|
||||
.command("increment", increment)
|
||||
.state(AtomicU32::new(0))
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Types
|
||||
|
||||
If you're bringing your existing Rust library with your own types, you can easily define how they are converted to and from Arma.
|
||||
|
||||
```rust
|
||||
use arma_rs::{FromArma, IntoArma, Value, FromArmaError};
|
||||
|
||||
pub struct MemoryReport {
|
||||
total: u64,
|
||||
free: u64,
|
||||
avail: u64,
|
||||
}
|
||||
|
||||
impl FromArma for MemoryReport {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let (total, free, avail) = <(u64, u64, u64)>::from_arma(s)?;
|
||||
Ok(Self { total, free, avail })
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoArma for MemoryReport {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Array(
|
||||
vec![self.total, self.free, self.avail]
|
||||
.into_iter()
|
||||
.map(|v| v.to_string().to_arma())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Derive
|
||||
|
||||
Alternatively you can derive these traits. Note that the derive and manual implementation examples slightly differ, as when deriving map like structs its represented as an hashmap rather than an array. For more information on data representation and attributes see: [FromArma](https://docs.rs/arma-rs/latest/arma_rs/derive.FromArma.html) and [IntoArma](https://docs.rs/arma-rs/latest/arma_rs/derive.IntoArma.html).
|
||||
|
||||
```rust
|
||||
use arma_rs::{FromArma, IntoArma};
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
struct MemoryReport {
|
||||
#[arma(to_string)]
|
||||
total: u64,
|
||||
#[arma(to_string)]
|
||||
free: u64,
|
||||
#[arma(to_string)]
|
||||
avail: u64,
|
||||
}
|
||||
```
|
||||
|
||||
Deriving is currently only supported for structs, this might change in the future.
|
||||
|
||||
## Error Codes
|
||||
|
||||
By default arma-rs will only allow commands via `RvExtensionArgs`. Using `callExtension` with only a function name will return an empty string.
|
||||
|
||||
```sqf
|
||||
"my_extension" callExtension "hello:english" // returns ""
|
||||
"my_extension" callExtension ["hello:english", []] // returns ["Hello", 0, 0]
|
||||
```
|
||||
|
||||
This behaviour can be changed by calling `.allow_no_args()` when building the extension. It is recommended not to use this, and to implement error handling instead.
|
||||
|
||||
| Code | Description |
|
||||
|------|---------------------------------------------------|
|
||||
| 0 | Success |
|
||||
| 1 | Command not found |
|
||||
| 2x | Invalid argument count, x is received count |
|
||||
| 3x | Invalid argument type, x is argument position |
|
||||
| 4 | Attempted to write a value larger than the buffer |
|
||||
| 9 | Application error, from using a Result |
|
||||
|
||||
### Error Examples
|
||||
|
||||
```rust
|
||||
use arma_rs::Context;
|
||||
|
||||
pub fn add(a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
|
||||
pub fn overflow(ctx: Context) -> String {
|
||||
"X".repeat(ctx.buffer_len() + 1)
|
||||
}
|
||||
|
||||
pub fn should_error(error: bool) -> Result<String, String> {
|
||||
if error {
|
||||
Err(String::from("told to error"))
|
||||
} else {
|
||||
Ok(String::from("told to succeed"))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```sqf
|
||||
"my_extension" callExtension ["add", [1, 2]]; // Returns ["3", 0, 0]
|
||||
"my_extension" callExtension ["sub", [1, 2]]; // Returns ["", 1, 0]
|
||||
"my_extension" callExtension ["add", [1, 2, 3]]; // Returns ["", 23, 0], didn't expect 3 elements
|
||||
"my_extension" callExtension ["add", [1, "two"]]; // Returns ["", 31, 0], unable to parse the second argument
|
||||
"my_extension" callExtension ["overflow", []]; // Returns ["", 4, 0], the return size was larger than the buffer
|
||||
"my_extension" callExtension ["should_error", [true]]; // Returns ["told to error", 9, 0]
|
||||
"my_extension" callExtension ["should_error", [false]]; // Returns ["told to succeed", 0, 0]
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Tests can be created utilizing the `extension.call()` method.
|
||||
|
||||
```rust,ignore
|
||||
mod tests {
|
||||
#[test]
|
||||
fn hello() {
|
||||
let extension = init().testing();
|
||||
let (output, _) = extension.call("hello:english", None);
|
||||
assert_eq!(output, "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn welcome() {
|
||||
let extension = init().testing();
|
||||
let (output, _) =
|
||||
extension.call("welcome:english", Some(vec!["John".to_string()]));
|
||||
assert_eq!(output, "Welcome John");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sleep_1sec() {
|
||||
let extension = Extension::build()
|
||||
.group("timer", super::group())
|
||||
.finish()
|
||||
.testing();
|
||||
let (_, code) = extension.call(
|
||||
"timer:sleep",
|
||||
Some(vec!["1".to_string(), "test".to_string()]),
|
||||
);
|
||||
assert_eq!(code, 0);
|
||||
let result = extension.callback_handler(
|
||||
|name, func, data| {
|
||||
assert_eq!(name, "timer:sleep");
|
||||
assert_eq!(func, "done");
|
||||
if let Some(Value::String(s)) = data {
|
||||
Result::Ok(s)
|
||||
} else {
|
||||
Result::Err("Data was not a string".to_string())
|
||||
}
|
||||
},
|
||||
Duration::from_secs(2),
|
||||
);
|
||||
assert_eq!(Result::Ok("test".to_string()), result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Unit Loadout Array
|
||||
|
||||
arma-rs includes a [loadout module](https://docs.rs/arma-rs/latest/arma_rs/loadout/index.html) to assist with the handling of [Arma's Unit Loadout Array](https://community.bistudio.com/wiki/Unit_Loadout_Array).
|
||||
|
||||
```rust
|
||||
use arma_rs::{FromArma, loadout::{Loadout, InventoryItem, Weapon, Magazine}};
|
||||
|
||||
let l = r#"[[],[],[],["U_Marshal",[]],[],[],"H_Cap_headphones","G_Aviator",[],["ItemMap","ItemGPS","","ItemCompass","ItemWatch",""]]"#;
|
||||
let mut loadout = Loadout::from_arma(l.to_string()).unwrap();
|
||||
loadout.set_secondary({
|
||||
let mut weapon = Weapon::new("launch_B_Titan_short_F".to_string());
|
||||
weapon.set_primary_magazine(Magazine::new("Titan_AT".to_string(), 1));
|
||||
weapon
|
||||
});
|
||||
loadout.set_primary({
|
||||
let mut weapon = Weapon::new("arifle_MXC_F".to_string());
|
||||
weapon.set_optic("optic_Holosight".to_string());
|
||||
weapon
|
||||
});
|
||||
let uniform = loadout.uniform_mut();
|
||||
uniform.set_class("U_B_CombatUniform_mcam".to_string());
|
||||
let uniform_items = uniform.items_mut().unwrap();
|
||||
uniform_items.push(InventoryItem::new_item("FirstAidKit".to_string(), 3));
|
||||
uniform_items.push(InventoryItem::new_magazine("30Rnd_65x39_caseless_mag".to_string(), 5, 30));
|
||||
```
|
||||
|
||||
## Common Rust Libraries
|
||||
|
||||
arma-rs supports some common Rust libraries.
|
||||
You can enable their support by adding their name to the features of arma-rs.
|
||||
|
||||
```toml
|
||||
arma-rs = { version = "1.8.0", features = ["chrono"] }
|
||||
```
|
||||
|
||||
Please create an issue first if you would like to add support for a new library.
|
||||
|
||||
### chrono
|
||||
|
||||
[`crates.io`](https://crates.io/crates/chrono)
|
||||
|
||||
#### chrono - Convert to Arma
|
||||
|
||||
[`NaiveDateTime`](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDateTime.html) and [`DateTime<TimeZone>`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) will be converted to [Arma's date array](https://community.bistudio.com/wiki/systemTimeUTC).
|
||||
The timezone will always be converted to UTC.
|
||||
|
||||
#### chrono - Convert From Arma
|
||||
|
||||
[Arma's date array](https://community.bistudio.com/wiki/systemTimeUTC) can be converted to [`NaiveDateTime`](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDateTime.html).
|
||||
|
||||
### uuid
|
||||
|
||||
[`crates.io`](https://crates.io/crates/uuid)
|
||||
|
||||
#### uuid - Convert To Arma
|
||||
|
||||
[`Uuid`](https://docs.rs/uuid/latest/uuid/struct.Uuid.html) will be converted to a string.
|
||||
|
||||
### serde_json
|
||||
|
||||
[`crates.io`](https://crates.io/crates/serde_json)
|
||||
|
||||
#### serde_json - Convert To Arma
|
||||
|
||||
Any variant of [`serde_json::Value`](https://docs.serde.rs/serde_json/enum.Value.html) will be converted to the appropriate Arma type.
|
||||
|
||||
## Building for x86 (32 Bit)
|
||||
|
||||
```sh
|
||||
rustup toolchain install stable-i686-pc-windows-msvc
|
||||
cargo +stable-i686-pc-windows-msvc build
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
||||
16
vendor/arma-rs/build.rs
vendored
Normal file
16
vendor/arma-rs/build.rs
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
let mut root = Path::new("../../README.md");
|
||||
if !root.exists() {
|
||||
root = Path::new("../README.md");
|
||||
}
|
||||
if !root.exists() {
|
||||
root = Path::new("README.md");
|
||||
}
|
||||
std::fs::copy(
|
||||
root,
|
||||
Path::new(&format!("{}/README.md", std::env::var("OUT_DIR").unwrap())),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
40
vendor/arma-rs/examples/hello_world.rs
vendored
Normal file
40
vendor/arma-rs/examples/hello_world.rs
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
use arma_rs::{arma, Extension};
|
||||
|
||||
#[arma]
|
||||
fn init() -> Extension {
|
||||
Extension::build()
|
||||
.version("1.0.0".to_string())
|
||||
.command("hello", hello)
|
||||
.command("welcome", welcome)
|
||||
.finish()
|
||||
}
|
||||
|
||||
pub fn hello() -> &'static str {
|
||||
"Hello"
|
||||
}
|
||||
|
||||
pub fn welcome(name: String) -> String {
|
||||
format!("Welcome {name}")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::init;
|
||||
|
||||
#[test]
|
||||
fn hello() {
|
||||
let extension = init().testing();
|
||||
let (result, _) = extension.call("hello", None);
|
||||
assert_eq!(result, "Hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn welcome() {
|
||||
let extension = init().testing();
|
||||
let (result, _) = extension.call("welcome", Some(vec!["John".to_string()]));
|
||||
assert_eq!(result, "Welcome John");
|
||||
}
|
||||
}
|
||||
|
||||
// Only required for cargo, don't include in your library
|
||||
fn main() {}
|
||||
364
vendor/arma-rs/src/call_context/call.rs
vendored
Normal file
364
vendor/arma-rs/src/call_context/call.rs
vendored
Normal file
@@ -0,0 +1,364 @@
|
||||
use std::path::Path;
|
||||
|
||||
use super::stack::ArmaContextStackTrace;
|
||||
|
||||
#[repr(C)]
|
||||
struct RawArmaCallContext {
|
||||
pub steam_id: u64,
|
||||
pub source: *const libc::c_char,
|
||||
pub mission: *const libc::c_char,
|
||||
pub server: *const libc::c_char,
|
||||
pub remote_exec_owner: i16,
|
||||
pub call_stack: Option<*const super::stack::RawContextStackTrace>,
|
||||
}
|
||||
|
||||
impl RawArmaCallContext {
|
||||
fn from_arma(args: *mut *mut i8, count: libc::c_int) -> Self {
|
||||
let steam_id = unsafe { *args.offset(0) as u64 };
|
||||
let source = unsafe { *args.offset(1) as *const libc::c_char };
|
||||
let mission = unsafe { *args.offset(2) as *const libc::c_char };
|
||||
let server = unsafe { *args.offset(3) as *const libc::c_char };
|
||||
let remote_exec_owner = unsafe { *args.offset(4) as i16 };
|
||||
|
||||
let call_stack = if count > 5 {
|
||||
let stack = unsafe { *args.offset(5) as *const super::stack::RawContextStackTrace };
|
||||
Some(stack)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self {
|
||||
steam_id,
|
||||
source,
|
||||
mission,
|
||||
server,
|
||||
remote_exec_owner,
|
||||
call_stack,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StackRequest {}
|
||||
|
||||
pub struct WithStackTrace;
|
||||
impl StackRequest for WithStackTrace {}
|
||||
|
||||
pub struct WithoutStackTrace;
|
||||
impl StackRequest for WithoutStackTrace {}
|
||||
|
||||
/// Context of the callExtension, provided by Arma.
|
||||
pub type CallContext = ArmaCallContext<WithoutStackTrace>;
|
||||
/// Context of the callExtension, provided by Arma, with a stack trace.
|
||||
pub type CallContextStackTrace = ArmaCallContext<WithStackTrace>;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
/// Context of the Arma call.
|
||||
pub struct ArmaCallContext<T: StackRequest> {
|
||||
pub(super) caller: Caller,
|
||||
pub(super) source: Source,
|
||||
pub(super) mission: Mission,
|
||||
pub(super) server: Server,
|
||||
pub(super) remote_exec_owner: i16,
|
||||
|
||||
_stack_marker: std::marker::PhantomData<T>,
|
||||
stack: Option<ArmaContextStackTrace>,
|
||||
}
|
||||
|
||||
impl<T: StackRequest> ArmaCallContext<T> {
|
||||
pub(crate) const fn new(
|
||||
caller: Caller,
|
||||
source: Source,
|
||||
mission: Mission,
|
||||
server: Server,
|
||||
remote_exec_owner: i16,
|
||||
) -> Self {
|
||||
Self {
|
||||
caller,
|
||||
source,
|
||||
mission,
|
||||
server,
|
||||
remote_exec_owner,
|
||||
|
||||
_stack_marker: std::marker::PhantomData,
|
||||
stack: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new ArmaCallContext from pointers provided by Arma.
|
||||
pub fn from_arma(args: *mut *mut i8, count: libc::c_int) -> Self {
|
||||
let raw = RawArmaCallContext::from_arma(args, count);
|
||||
Self {
|
||||
caller: Caller::Steam(raw.steam_id),
|
||||
source: Source::from(unsafe { std::ffi::CStr::from_ptr(raw.source).to_str().unwrap() }),
|
||||
mission: Mission::from(unsafe {
|
||||
std::ffi::CStr::from_ptr(raw.mission).to_str().unwrap()
|
||||
}),
|
||||
server: Server::from(unsafe { std::ffi::CStr::from_ptr(raw.server).to_str().unwrap() }),
|
||||
remote_exec_owner: raw.remote_exec_owner,
|
||||
|
||||
_stack_marker: std::marker::PhantomData,
|
||||
stack: raw.call_stack.map(ArmaContextStackTrace::from),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Player that called the extension. Can be [`Caller::Unknown`] when the player's steamID64 is unavailable
|
||||
/// # Note
|
||||
/// Unlike <https://community.bistudio.com/wiki/getPlayerUID> [`Caller::Steam`] isn't limited to multiplayer.
|
||||
pub fn caller(&self) -> &Caller {
|
||||
&self.caller
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Source from where the extension was called.
|
||||
pub fn source(&self) -> &Source {
|
||||
&self.source
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Current mission's name.
|
||||
/// # Note
|
||||
/// Can result in [`Mission::None`] in missions made prior to Arma v2.02.
|
||||
pub fn mission(&self) -> &Mission {
|
||||
&self.mission
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Current server's name
|
||||
pub fn server(&self) -> &Server {
|
||||
&self.server
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Remote execution owner.
|
||||
pub fn remote_exec_owner(&self) -> i16 {
|
||||
self.remote_exec_owner
|
||||
}
|
||||
}
|
||||
|
||||
impl ArmaCallContext<WithStackTrace> {
|
||||
#[must_use]
|
||||
/// Call stack of the extension call.
|
||||
pub fn stack_trace(&self) -> &ArmaContextStackTrace {
|
||||
// By the time this gets to consumer code, to_without_stack would've been called if the stack was not requested
|
||||
self.stack.as_ref().expect("Stack is missing")
|
||||
}
|
||||
|
||||
/// Convert the context to one without a stack trace.
|
||||
pub(crate) fn into_without_stack(self) -> ArmaCallContext<WithoutStackTrace> {
|
||||
ArmaCallContext::new(
|
||||
self.caller,
|
||||
self.source,
|
||||
self.mission,
|
||||
self.server,
|
||||
self.remote_exec_owner,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Identification of the player calling your extension.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub enum Caller {
|
||||
/// The player's steamID64.
|
||||
Steam(u64),
|
||||
#[default]
|
||||
/// Unable to determine.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Caller {
|
||||
/// Convert the caller to a string.
|
||||
pub fn as_str(&self) -> String {
|
||||
match self {
|
||||
Self::Steam(id) => id.to_string(),
|
||||
Self::Unknown => "0".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the caller to a u64.
|
||||
pub fn as_u64(&self) -> u64 {
|
||||
match self {
|
||||
Self::Steam(id) => *id,
|
||||
Self::Unknown => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Caller {
|
||||
fn from(s: &str) -> Self {
|
||||
if s.is_empty() || s == "0" {
|
||||
Self::Unknown
|
||||
} else {
|
||||
s.parse::<u64>().map_or(Self::Unknown, Self::Steam)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Caller {
|
||||
fn from(id: u64) -> Self {
|
||||
if id == 0 {
|
||||
Self::Unknown
|
||||
} else {
|
||||
Self::Steam(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Source of the extension call.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub enum Source {
|
||||
/// Absolute path of the file on the players system.
|
||||
/// For example on windows: `C:\Users\user\Documents\Arma 3\missions\test.VR\fn_armaContext.sqf`.
|
||||
File(String),
|
||||
/// Path inside of a pbo.
|
||||
/// For example: `z\test\addons\main\fn_armaContext.sqf`.
|
||||
Pbo(String),
|
||||
#[default]
|
||||
/// Debug console or an other form of on the fly execution, such as mission triggers.
|
||||
Console,
|
||||
}
|
||||
|
||||
impl Source {
|
||||
/// Convert the source to a string.
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::File(s) | Self::Pbo(s) => s,
|
||||
Self::Console => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Source {
|
||||
fn from(s: &str) -> Self {
|
||||
if s.is_empty() {
|
||||
Self::Console
|
||||
} else if Path::new(s).is_absolute() {
|
||||
Self::File(s.to_string())
|
||||
} else {
|
||||
Self::Pbo(s.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<*const libc::c_char> for Source {
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
fn from(s: *const libc::c_char) -> Self {
|
||||
Self::from(unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Current mission.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub enum Mission {
|
||||
/// Mission name.
|
||||
Mission(String),
|
||||
#[default]
|
||||
/// Not in a mission.
|
||||
None,
|
||||
}
|
||||
|
||||
impl Mission {
|
||||
/// Convert the mission to a string.
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::Mission(s) => s,
|
||||
Self::None => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Mission {
|
||||
fn from(s: &str) -> Self {
|
||||
if s.is_empty() {
|
||||
Self::None
|
||||
} else {
|
||||
Self::Mission(s.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<*const libc::c_char> for Mission {
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
fn from(s: *const libc::c_char) -> Self {
|
||||
Self::from(unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Current server.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub enum Server {
|
||||
/// Server name
|
||||
Multiplayer(String),
|
||||
#[default]
|
||||
/// Singleplayer or no mission
|
||||
Singleplayer,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
/// Convert the server to a string.
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::Multiplayer(s) => s,
|
||||
Self::Singleplayer => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Server {
|
||||
fn from(s: &str) -> Self {
|
||||
if s.is_empty() {
|
||||
Self::Singleplayer
|
||||
} else {
|
||||
Self::Multiplayer(s.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<*const libc::c_char> for Server {
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
fn from(s: *const libc::c_char) -> Self {
|
||||
Self::from(unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap() })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn caller_empty() {
|
||||
assert_eq!(Caller::from(""), Caller::Unknown);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caller_zero() {
|
||||
assert_eq!(Caller::from("0"), Caller::Unknown);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_empty() {
|
||||
assert_eq!(Source::from(""), Source::Console);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_pbo() {
|
||||
let path = "x\\ctx\\addons\\main\\fn_armaContext.sqf";
|
||||
assert_eq!(Source::from(path), Source::Pbo(path.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_file() {
|
||||
let path = env!("CARGO_MANIFEST_DIR");
|
||||
assert_eq!(Source::from(path), Source::File(path.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mission_empty() {
|
||||
assert_eq!(Mission::from(""), Mission::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_empty() {
|
||||
assert_eq!(Server::from(""), Server::Singleplayer);
|
||||
}
|
||||
}
|
||||
40
vendor/arma-rs/src/call_context/manager.rs
vendored
Normal file
40
vendor/arma-rs/src/call_context/manager.rs
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::ContextRequest;
|
||||
|
||||
use super::CallContextStackTrace;
|
||||
|
||||
/// Manages requesting and replacing the ArmaCallContext
|
||||
pub struct ArmaContextManager {
|
||||
pub(crate) request: RefCell<ContextRequest>,
|
||||
state: Rc<RefCell<Option<CallContextStackTrace>>>,
|
||||
}
|
||||
|
||||
impl ArmaContextManager {
|
||||
/// Create a new ArmaContextManager
|
||||
pub fn new(request: ContextRequest) -> Self {
|
||||
Self {
|
||||
request: RefCell::new(request),
|
||||
state: Rc::new(RefCell::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Request a new ArmaCallContext from Arma
|
||||
pub fn request(&self) -> CallContextStackTrace {
|
||||
// When the request is called, Arma will send the request to the extension
|
||||
// The extension will set the state to the request it just received
|
||||
unsafe {
|
||||
(self.request.borrow())();
|
||||
}
|
||||
// When the request function returns, the state has been set by Arma
|
||||
// It can now be taken and sent to the Context
|
||||
self.state
|
||||
.replace(None)
|
||||
.expect("Arma should've set the state")
|
||||
}
|
||||
|
||||
/// Replace the current ArmaCallContext with a new one
|
||||
pub fn replace(&self, value: Option<CallContextStackTrace>) {
|
||||
*self.state.borrow_mut() = value;
|
||||
}
|
||||
}
|
||||
6
vendor/arma-rs/src/call_context/mod.rs
vendored
Normal file
6
vendor/arma-rs/src/call_context/mod.rs
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
mod call;
|
||||
mod manager;
|
||||
mod stack;
|
||||
|
||||
pub use call::*;
|
||||
pub use manager::ArmaContextManager;
|
||||
72
vendor/arma-rs/src/call_context/stack.rs
vendored
Normal file
72
vendor/arma-rs/src/call_context/stack.rs
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
use std::ffi::CStr;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct RawStackTraceLine {
|
||||
// Line number in file (before preprocessing if preprocessed with line numbers)
|
||||
pub line_number: u32,
|
||||
// File offset in bytes from the start of the file (after preprocessing)
|
||||
pub file_offset: u32,
|
||||
// Filepath to the source file
|
||||
pub source_file: *const libc::c_char,
|
||||
// scopeName set on that level
|
||||
pub scope_name: *const libc::c_char,
|
||||
// Complete fileContent of the sourceFile (after preprocessing, can be combined with fileOffset to find exact location)
|
||||
pub file_content: *const libc::c_char,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct RawContextStackTrace {
|
||||
pub lines: *mut RawStackTraceLine,
|
||||
pub line_count: u32,
|
||||
}
|
||||
|
||||
impl RawContextStackTrace {
|
||||
pub fn to_lines(&self) -> Option<&[RawStackTraceLine]> {
|
||||
unsafe {
|
||||
self.lines
|
||||
.as_ref()
|
||||
.map(|lines_ptr| std::slice::from_raw_parts(lines_ptr, self.line_count as usize))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ArmaContextStackTrace {
|
||||
pub lines: Vec<ArmaStackTraceLine>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ArmaStackTraceLine {
|
||||
pub line_number: u32,
|
||||
pub file_offset: u32,
|
||||
pub source_file: String,
|
||||
pub scope_name: String,
|
||||
pub file_content: String,
|
||||
}
|
||||
|
||||
impl From<*const RawContextStackTrace> for ArmaContextStackTrace {
|
||||
fn from(raw: *const RawContextStackTrace) -> Self {
|
||||
unsafe {
|
||||
let raw = raw.as_ref().unwrap();
|
||||
let lines = raw
|
||||
.to_lines()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|line| ArmaStackTraceLine {
|
||||
line_number: line.line_number,
|
||||
file_offset: line.file_offset,
|
||||
source_file: CStr::from_ptr(line.source_file)
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
scope_name: CStr::from_ptr(line.scope_name)
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
file_content: CStr::from_ptr(line.file_content)
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
})
|
||||
.collect();
|
||||
Self { lines }
|
||||
}
|
||||
}
|
||||
}
|
||||
334
vendor/arma-rs/src/command.rs
vendored
Normal file
334
vendor/arma-rs/src/command.rs
vendored
Normal file
@@ -0,0 +1,334 @@
|
||||
use crate::call_context::{ArmaContextManager, CallContext, CallContextStackTrace};
|
||||
use crate::ext_result::IntoExtResult;
|
||||
use crate::flags::FeatureFlags;
|
||||
use crate::value::{FromArma, Value};
|
||||
use crate::Context;
|
||||
|
||||
type HandlerFunc = Box<
|
||||
dyn Fn(
|
||||
Context,
|
||||
&ArmaContextManager,
|
||||
*mut libc::c_char,
|
||||
libc::size_t,
|
||||
Option<*mut *mut i8>,
|
||||
Option<libc::c_int>,
|
||||
) -> libc::c_int,
|
||||
>;
|
||||
|
||||
#[doc(hidden)]
|
||||
/// A wrapper for `HandlerFunc`
|
||||
pub struct Handler {
|
||||
/// The function to call
|
||||
pub handler: HandlerFunc,
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Create a new handler from a Factory
|
||||
pub fn fn_handler<C, I, R>(command: C) -> Handler
|
||||
where
|
||||
C: Factory<I, R> + 'static,
|
||||
{
|
||||
Handler {
|
||||
handler: Box::new(
|
||||
move |context: Context,
|
||||
acm: &ArmaContextManager,
|
||||
output: *mut libc::c_char,
|
||||
size: libc::size_t,
|
||||
args: Option<*mut *mut i8>,
|
||||
count: Option<libc::c_int>|
|
||||
-> libc::c_int {
|
||||
unsafe { command.call(context, acm, output, size, args, count) }
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Execute a command
|
||||
pub trait Executor: 'static {
|
||||
/// # Safety
|
||||
/// This function is unsafe because it interacts with the C API.
|
||||
unsafe fn call(
|
||||
&self,
|
||||
context: Context,
|
||||
acm: &ArmaContextManager,
|
||||
output: *mut libc::c_char,
|
||||
size: libc::size_t,
|
||||
args: Option<*mut *mut i8>,
|
||||
count: Option<libc::c_int>,
|
||||
);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// A factory for creating a command handler.
|
||||
/// Creates a handler from any function that optionally takes a context and up to 12 arguments.
|
||||
/// The arguments must implement `FromArma`
|
||||
/// The return value must implement `IntoExtResult`
|
||||
pub trait Factory<A, R> {
|
||||
/// # Safety
|
||||
/// This function is unsafe because it interacts with the C API.
|
||||
unsafe fn call(
|
||||
&self,
|
||||
context: Context,
|
||||
acm: &ArmaContextManager,
|
||||
output: *mut libc::c_char,
|
||||
size: libc::size_t,
|
||||
args: Option<*mut *mut i8>,
|
||||
count: Option<libc::c_int>,
|
||||
) -> libc::c_int;
|
||||
}
|
||||
|
||||
macro_rules! execute {
|
||||
($s:ident, $c:expr, $count:expr, $output:expr, $size:expr, $args:expr, ($( $vars:ident )*), ($( $param:ident, )*)) => {{
|
||||
let count = $count.unwrap_or_else(|| 0);
|
||||
if count != $c {
|
||||
return format!("2{}", count).parse::<libc::c_int>().unwrap();
|
||||
}
|
||||
if $c == 0 {
|
||||
handle_output_and_return(
|
||||
($s)($( $vars, )* $($param::from_arma("".to_string()).unwrap(),)*),
|
||||
$output,
|
||||
$size
|
||||
)
|
||||
} else {
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
let mut argv: Vec<String> = {
|
||||
let argv: &[*mut libc::c_char; $c] = &*($args.unwrap() as *const [*mut i8; $c]);
|
||||
let mut argv = argv
|
||||
.to_vec()
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
std::ffi::CStr::from_ptr(s).to_string_lossy().to_string()
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
argv.reverse();
|
||||
argv
|
||||
};
|
||||
#[allow(unused_variables, unused_mut)] // Caused by the 0 loop
|
||||
let mut c = 0;
|
||||
#[allow(unused_assignments, clippy::mixed_read_write_in_expression)]
|
||||
handle_output_and_return(
|
||||
{
|
||||
($s)($( $vars, )* $(
|
||||
if let Ok(val) = $param::from_arma(argv.pop().unwrap()) {
|
||||
c += 1;
|
||||
val
|
||||
} else {
|
||||
return format!("3{}", c).parse::<libc::c_int>().unwrap()
|
||||
},
|
||||
)*)
|
||||
},
|
||||
$output,
|
||||
$size
|
||||
)
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! factory_tuple ({ $c: expr, $($param:ident)* } => {
|
||||
impl<$($param,)* ER> Executor for dyn Factory<($($param,)*), ER>
|
||||
where
|
||||
ER: 'static,
|
||||
$($param: FromArma + 'static,)*
|
||||
{
|
||||
unsafe fn call(
|
||||
&self,
|
||||
context: Context,
|
||||
acm: &ArmaContextManager,
|
||||
output: *mut libc::c_char,
|
||||
size: libc::size_t,
|
||||
args: Option<*mut *mut i8>,
|
||||
count: Option<libc::c_int>,
|
||||
) {
|
||||
self.call(context, acm, output, size, args, count);
|
||||
}
|
||||
}
|
||||
|
||||
// No context
|
||||
impl<Func, $($param,)* ER> Factory<($($param,)*), ER> for Func
|
||||
where
|
||||
ER: IntoExtResult + 'static,
|
||||
Func: Fn($($param),*) -> ER,
|
||||
$($param: FromArma,)*
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn call(&self, _: Context, _: &ArmaContextManager, output: *mut libc::c_char, size: libc::size_t, args: Option<*mut *mut i8>, count: Option<libc::c_int>) -> libc::c_int {
|
||||
let count = count.unwrap_or_else(|| 0);
|
||||
if count != $c {
|
||||
return format!("2{}", count).parse::<libc::c_int>().unwrap();
|
||||
}
|
||||
if $c == 0 {
|
||||
handle_output_and_return(
|
||||
(self)($($param::from_arma("".to_string()).unwrap(),)*),
|
||||
output,
|
||||
size
|
||||
)
|
||||
} else {
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
let mut argv: Vec<String> = {
|
||||
let argv: &[*mut libc::c_char; $c] = &*(args.unwrap() as *const [*mut i8; $c]);
|
||||
let mut argv = argv
|
||||
.to_vec()
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
std::ffi::CStr::from_ptr(s).to_string_lossy().to_string()
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
argv.reverse();
|
||||
argv
|
||||
};
|
||||
#[allow(unused_variables, unused_mut)] // Caused by the 0 loop
|
||||
let mut c = 0;
|
||||
#[allow(unused_assignments, clippy::mixed_read_write_in_expression)]
|
||||
handle_output_and_return(
|
||||
{
|
||||
(self)($(
|
||||
if let Ok(val) = $param::from_arma(argv.pop().unwrap()) {
|
||||
c += 1;
|
||||
val
|
||||
} else {
|
||||
return format!("3{}", c).parse::<libc::c_int>().unwrap()
|
||||
},
|
||||
)*)
|
||||
},
|
||||
output,
|
||||
size
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Context
|
||||
impl<Func, $($param,)* ER> Factory<(Context, $($param,)*), ER> for Func
|
||||
where
|
||||
ER: IntoExtResult + 'static,
|
||||
Func: Fn(Context, $($param),*) -> ER,
|
||||
$($param: FromArma,)*
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn call(&self, context: Context, _: &ArmaContextManager, output: *mut libc::c_char, size: libc::size_t, args: Option<*mut *mut i8>, count: Option<libc::c_int>) -> libc::c_int {
|
||||
execute!(self, $c, count, output, size, args, (context), ($($param,)*))
|
||||
}
|
||||
}
|
||||
|
||||
// Call Context
|
||||
impl<Func, $($param,)* ER> Factory<(CallContext, $($param,)*), ER> for Func
|
||||
where
|
||||
ER: IntoExtResult + 'static,
|
||||
Func: Fn(CallContext, $($param),*) -> ER,
|
||||
$($param: FromArma,)*
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn call(&self, _: Context, acm: &ArmaContextManager, output: *mut libc::c_char, size: libc::size_t, args: Option<*mut *mut i8>, count: Option<libc::c_int>) -> libc::c_int {
|
||||
crate::RVExtensionFeatureFlags = FeatureFlags::default().with_context_stack_trace(false).as_bits();
|
||||
let call_context = acm.request().into_without_stack();
|
||||
execute!(self, $c, count, output, size, args, (call_context), ($($param,)*))
|
||||
}
|
||||
}
|
||||
|
||||
// Call Context with Stack Trace
|
||||
impl<Func, $($param,)* ER> Factory<(CallContextStackTrace, $($param,)*), ER> for Func
|
||||
where
|
||||
ER: IntoExtResult + 'static,
|
||||
Func: Fn(CallContextStackTrace, $($param),*) -> ER,
|
||||
$($param: FromArma,)*
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn call(&self, _: Context, acm: &ArmaContextManager, output: *mut libc::c_char, size: libc::size_t, args: Option<*mut *mut i8>, count: Option<libc::c_int>) -> libc::c_int {
|
||||
crate::RVExtensionFeatureFlags = FeatureFlags::default().with_context_stack_trace(true).as_bits();
|
||||
let call_context = acm.request();
|
||||
execute!(self, $c, count, output, size, args, (call_context), ($($param,)*))
|
||||
}
|
||||
}
|
||||
|
||||
// Context & Call Context
|
||||
impl<Func, $($param,)* ER> Factory<(Context, CallContext, $($param,)*), ER> for Func
|
||||
where
|
||||
ER: IntoExtResult + 'static,
|
||||
Func: Fn(Context, CallContext, $($param),*) -> ER,
|
||||
$($param: FromArma,)*
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn call(&self, context: Context, acm: &ArmaContextManager, output: *mut libc::c_char, size: libc::size_t, args: Option<*mut *mut i8>, count: Option<libc::c_int>) -> libc::c_int {
|
||||
crate::RVExtensionFeatureFlags = FeatureFlags::default().with_context_stack_trace(false).as_bits();
|
||||
let call_context = acm.request().into_without_stack();
|
||||
execute!(self, $c, count, output, size, args, (context call_context), ($($param,)*))
|
||||
}
|
||||
}
|
||||
|
||||
// Context & Call Context with Stack Trace
|
||||
impl<Func, $($param,)* ER> Factory<(Context, CallContextStackTrace, $($param,)*), ER> for Func
|
||||
where
|
||||
ER: IntoExtResult + 'static,
|
||||
Func: Fn(Context, CallContextStackTrace, $($param),*) -> ER,
|
||||
$($param: FromArma,)*
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn call(&self, context: Context, acm: &ArmaContextManager, output: *mut libc::c_char, size: libc::size_t, args: Option<*mut *mut i8>, count: Option<libc::c_int>) -> libc::c_int {
|
||||
crate::RVExtensionFeatureFlags = FeatureFlags::default().with_context_stack_trace(true).as_bits();
|
||||
let call_context = acm.request();
|
||||
execute!(self, $c, count, output, size, args, (context call_context), ($($param,)*))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
unsafe fn handle_output_and_return<R>(
|
||||
ret: R,
|
||||
output: *mut libc::c_char,
|
||||
size: libc::size_t,
|
||||
) -> libc::c_int
|
||||
where
|
||||
R: IntoExtResult + 'static,
|
||||
{
|
||||
let ret = ret.to_ext_result();
|
||||
let ok = ret.is_ok();
|
||||
if crate::write_cstr(
|
||||
{
|
||||
let value = match ret {
|
||||
Ok(x) | Err(x) => x,
|
||||
};
|
||||
match value {
|
||||
Value::String(s) => s,
|
||||
v => v.to_string(),
|
||||
}
|
||||
},
|
||||
output,
|
||||
size,
|
||||
)
|
||||
.is_none()
|
||||
{
|
||||
4
|
||||
} else if ok {
|
||||
0
|
||||
} else {
|
||||
9
|
||||
}
|
||||
}
|
||||
|
||||
factory_tuple! { 0, }
|
||||
factory_tuple! { 1, A }
|
||||
factory_tuple! { 2, A B }
|
||||
factory_tuple! { 3, A B C }
|
||||
factory_tuple! { 4, A B C D }
|
||||
factory_tuple! { 5, A B C D E }
|
||||
factory_tuple! { 6, A B C D E F }
|
||||
factory_tuple! { 7, A B C D E F G }
|
||||
factory_tuple! { 8, A B C D E F G H }
|
||||
factory_tuple! { 9, A B C D E F G H I }
|
||||
factory_tuple! { 10, A B C D E F G H I J }
|
||||
factory_tuple! { 11, A B C D E F G H I J K }
|
||||
factory_tuple! { 12, A B C D E F G H I J K L }
|
||||
factory_tuple! { 13, A B C D E F G H I J K L M }
|
||||
factory_tuple! { 14, A B C D E F G H I J K L M N }
|
||||
factory_tuple! { 15, A B C D E F G H I J K L M N O }
|
||||
factory_tuple! { 16, A B C D E F G H I J K L M N O P }
|
||||
factory_tuple! { 17, A B C D E F G H I J K L M N O P Q }
|
||||
factory_tuple! { 18, A B C D E F G H I J K L M N O P Q R }
|
||||
factory_tuple! { 19, A B C D E F G H I J K L M N O P Q R S }
|
||||
factory_tuple! { 20, A B C D E F G H I J K L M N O P Q R S T }
|
||||
factory_tuple! { 21, A B C D E F G H I J K L M N O P Q R S T U }
|
||||
factory_tuple! { 22, A B C D E F G H I J K L M N O P Q R S T U V }
|
||||
factory_tuple! { 23, A B C D E F G H I J K L M N O P Q R S T U V W }
|
||||
factory_tuple! { 24, A B C D E F G H I J K L M N O P Q R S T U V W X }
|
||||
factory_tuple! { 25, A B C D E F G H I J K L M N O P Q R S T U V W X Y }
|
||||
factory_tuple! { 26, A B C D E F G H I J K L M N O P Q R S T U V W X Y Z }
|
||||
37
vendor/arma-rs/src/context/global.rs
vendored
Normal file
37
vendor/arma-rs/src/context/global.rs
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{ContextState, State};
|
||||
|
||||
/// Contains information about the extension
|
||||
pub struct GlobalContext {
|
||||
version: String,
|
||||
state: Arc<State>,
|
||||
}
|
||||
|
||||
impl GlobalContext {
|
||||
pub(crate) fn new(version: String, state: Arc<State>) -> Self {
|
||||
Self { version, state }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Version of the Arma extension
|
||||
pub fn version(&self) -> &str {
|
||||
&self.version
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextState for GlobalContext {
|
||||
fn get<T>(&self) -> Option<&T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
self.state.try_get()
|
||||
}
|
||||
|
||||
fn set<T>(&self, value: T) -> bool
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
self.state.set(value)
|
||||
}
|
||||
}
|
||||
30
vendor/arma-rs/src/context/group.rs
vendored
Normal file
30
vendor/arma-rs/src/context/group.rs
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{ContextState, State};
|
||||
|
||||
/// Contains information about the current group
|
||||
pub struct GroupContext {
|
||||
state: Arc<State>,
|
||||
}
|
||||
|
||||
impl GroupContext {
|
||||
pub(crate) fn new(state: Arc<State>) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextState for GroupContext {
|
||||
fn get<T>(&self) -> Option<&T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
self.state.try_get()
|
||||
}
|
||||
|
||||
fn set<T>(&self, value: T) -> bool
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
self.state.set(value)
|
||||
}
|
||||
}
|
||||
171
vendor/arma-rs/src/context/mod.rs
vendored
Normal file
171
vendor/arma-rs/src/context/mod.rs
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
//! Contextual execution information.
|
||||
|
||||
use crossbeam_channel::Sender;
|
||||
|
||||
use crate::{CallbackMessage, IntoArma, Value};
|
||||
|
||||
mod global;
|
||||
mod group;
|
||||
mod state;
|
||||
|
||||
pub use self::state::ContextState;
|
||||
pub use global::GlobalContext;
|
||||
pub use group::GroupContext;
|
||||
|
||||
/// Contains information about the current execution context
|
||||
pub struct Context {
|
||||
callback_tx: Sender<CallbackMessage>,
|
||||
global: GlobalContext,
|
||||
group: GroupContext,
|
||||
buffer_size: usize,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub(crate) fn new(
|
||||
callback_tx: Sender<CallbackMessage>,
|
||||
global: GlobalContext,
|
||||
group: GroupContext,
|
||||
) -> Self {
|
||||
Self {
|
||||
callback_tx,
|
||||
global,
|
||||
group,
|
||||
buffer_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_group(mut self, ctx: GroupContext) -> Self {
|
||||
self.group = ctx;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) const fn with_buffer_size(mut self, buffer_size: usize) -> Self {
|
||||
self.buffer_size = buffer_size;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Global context
|
||||
pub const fn global(&self) -> &GlobalContext {
|
||||
&self.global
|
||||
}
|
||||
|
||||
/// Group context, is equal to `GlobalContext` if the call is from the global scope.
|
||||
pub const fn group(&self) -> &GroupContext {
|
||||
&self.group
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Returns the length in bytes of the output buffer.
|
||||
/// This is the maximum size of the data that can be returned by the extension.
|
||||
pub const fn buffer_len(&self) -> usize {
|
||||
if self.buffer_size == 0 {
|
||||
0
|
||||
} else {
|
||||
self.buffer_size - 1
|
||||
}
|
||||
}
|
||||
|
||||
fn callback(&self, name: &str, func: &str, data: Option<Value>) -> Result<(), CallbackError> {
|
||||
self.callback_tx
|
||||
.send(CallbackMessage::Call(
|
||||
name.to_string(),
|
||||
func.to_string(),
|
||||
data,
|
||||
))
|
||||
.map_err(|_| CallbackError::ChannelClosed)
|
||||
}
|
||||
|
||||
/// Sends a callback with data into Arma
|
||||
/// <https://community.bistudio.com/wiki/Arma_3:_Mission_Event_Handlers#ExtensionCallback>
|
||||
pub fn callback_data<V>(&self, name: &str, func: &str, data: V) -> Result<(), CallbackError>
|
||||
where
|
||||
V: IntoArma,
|
||||
{
|
||||
self.callback(name, func, Some(data.to_arma()))
|
||||
}
|
||||
|
||||
/// Sends a callback without data into Arma
|
||||
/// <https://community.bistudio.com/wiki/Arma_3:_Mission_Event_Handlers#ExtensionCallback>
|
||||
pub fn callback_null(&self, name: &str, func: &str) -> Result<(), CallbackError> {
|
||||
self.callback(name, func, None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error that can occur when sending a callback
|
||||
#[derive(Debug)]
|
||||
pub enum CallbackError {
|
||||
/// The callback channel has been closed
|
||||
ChannelClosed,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CallbackError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::ChannelClosed => write!(f, "Callback channel closed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoArma for CallbackError {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::String(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::State;
|
||||
use crossbeam_channel::{bounded, Sender};
|
||||
use std::sync::Arc;
|
||||
|
||||
fn context(tx: Sender<CallbackMessage>) -> Context {
|
||||
Context::new(
|
||||
tx,
|
||||
GlobalContext::new(String::new(), Arc::new(State::default())),
|
||||
GroupContext::new(Arc::new(State::default())),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_buffer_len_zero() {
|
||||
let (tx, _) = bounded(0);
|
||||
assert_eq!(context(tx).buffer_len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_buffer_len() {
|
||||
let (tx, _) = bounded(0);
|
||||
assert_eq!(context(tx).with_buffer_size(100).buffer_len(), 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_callback_block() {
|
||||
let (tx, rx) = bounded(0);
|
||||
let callback_tx = tx.clone();
|
||||
std::thread::spawn(|| {
|
||||
context(callback_tx).callback_null("", "").unwrap();
|
||||
});
|
||||
let callback_tx = tx;
|
||||
std::thread::spawn(|| {
|
||||
context(callback_tx).callback_data("", "", "").unwrap();
|
||||
});
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
assert_eq!(rx.iter().count(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_callback_closed() {
|
||||
let (tx, _) = bounded(0);
|
||||
assert!(matches!(
|
||||
context(tx.clone()).callback_null("", ""),
|
||||
Err(CallbackError::ChannelClosed)
|
||||
));
|
||||
assert!(matches!(
|
||||
context(tx).callback_data("", "", ""),
|
||||
Err(CallbackError::ChannelClosed)
|
||||
));
|
||||
}
|
||||
}
|
||||
12
vendor/arma-rs/src/context/state.rs
vendored
Normal file
12
vendor/arma-rs/src/context/state.rs
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/// A trait for accessing state values
|
||||
pub trait ContextState {
|
||||
/// Get a reference to a state value
|
||||
fn get<T>(&self) -> Option<&T>
|
||||
where
|
||||
T: Send + Sync + 'static;
|
||||
|
||||
/// Set a state value
|
||||
fn set<T>(&self, value: T) -> bool
|
||||
where
|
||||
T: Send + Sync + 'static;
|
||||
}
|
||||
98
vendor/arma-rs/src/ext_result.rs
vendored
Normal file
98
vendor/arma-rs/src/ext_result.rs
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
use crate::value::{IntoArma, Value};
|
||||
|
||||
/// Convert a type to a successful or failed extension result
|
||||
pub trait IntoExtResult {
|
||||
/// Convert a type to a successful or failed extension result
|
||||
fn to_ext_result(&self) -> Result<Value, Value>;
|
||||
}
|
||||
|
||||
impl IntoExtResult for Value {
|
||||
fn to_ext_result(&self) -> Result<Value, Value> {
|
||||
Ok(self.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoExtResult for T
|
||||
where
|
||||
T: IntoArma,
|
||||
{
|
||||
fn to_ext_result(&self) -> Result<Value, Value> {
|
||||
self.to_arma().to_ext_result()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoExtResult for Result<Value, Value> {
|
||||
fn to_ext_result(&self) -> Result<Value, Value> {
|
||||
self.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> IntoExtResult for Result<T, E>
|
||||
where
|
||||
T: IntoArma,
|
||||
E: IntoArma,
|
||||
{
|
||||
fn to_ext_result(&self) -> Result<Value, Value> {
|
||||
match self {
|
||||
Ok(v) => Ok(v.to_arma()),
|
||||
Err(e) => Err(e.to_arma()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn value() {
|
||||
assert_eq!(
|
||||
Ok(Value::Boolean(true)),
|
||||
Value::Boolean(true).to_ext_result()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option_none() {
|
||||
assert_eq!(Ok(Value::Null), None::<&str>.to_ext_result());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option_some() {
|
||||
assert_eq!(
|
||||
Ok(Value::String("Hello".into())),
|
||||
Some("Hello".to_string()).to_ext_result()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_ok() {
|
||||
assert_eq!(
|
||||
Ok(Value::Number(42.0)),
|
||||
Ok(Value::Number(42.0)).to_ext_result()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_err() {
|
||||
assert_eq!(
|
||||
Err(Value::String("Hello".into())),
|
||||
Err(Value::String("Hello".into())).to_ext_result()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_unit_ok() {
|
||||
assert_eq!(Ok(Value::Null), Ok::<(), String>(()).to_ext_result());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_unit_err() {
|
||||
assert_eq!(Err(Value::Null), Err::<String, ()>(()).to_ext_result());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_unit_both() {
|
||||
assert_eq!(Ok(Value::Null), Ok::<(), ()>(()).to_ext_result());
|
||||
}
|
||||
}
|
||||
73
vendor/arma-rs/src/flags.rs
vendored
Normal file
73
vendor/arma-rs/src/flags.rs
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
//! Feature flags for RV Extensions
|
||||
//!
|
||||
//! <https://community.bistudio.com/wiki/Extensions#Feature_Flags>
|
||||
|
||||
/// RVExtensionContext takes const void** as argument, instead of the default const char**, and arguments will be passed in their custom types
|
||||
pub const RV_CONTEXT_ARGUMENTS_VOID_PTR: u64 = 1 << 0;
|
||||
/// RVExtensionContext will retrieve a full Stacktrace
|
||||
pub const RV_CONTEXT_STACK_TRACE: u64 = 1 << 1;
|
||||
/// RVExtensionContext will not be called automatically. It must be manually requested via RVExtensionRequestContext (This improves performance when context is not needed).
|
||||
pub const RV_CONTEXT_NO_DEFAULT_CALL: u64 = 1 << 2;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
/// Feature flags for RV Extensions
|
||||
pub struct FeatureFlags {
|
||||
context_stack_trace: bool,
|
||||
}
|
||||
|
||||
impl FeatureFlags {
|
||||
/// Set the context_stack_trace flag
|
||||
pub fn set_context_stack_trace(&mut self, value: bool) {
|
||||
self.context_stack_trace = value;
|
||||
}
|
||||
|
||||
pub fn with_context_stack_trace(mut self, value: bool) -> Self {
|
||||
self.set_context_stack_trace(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the context_stack_trace flag
|
||||
pub fn context_stack_trace(&self) -> bool {
|
||||
self.context_stack_trace
|
||||
}
|
||||
|
||||
/// Create a new FeatureFlags from the given bits
|
||||
pub fn from_bits(bits: u64) -> Self {
|
||||
let mut flags = Self::default();
|
||||
flags.set_context_stack_trace(bits & RV_CONTEXT_STACK_TRACE != 0);
|
||||
flags
|
||||
}
|
||||
|
||||
/// Get the bits of the FeatureFlags
|
||||
pub fn as_bits(&self) -> u64 {
|
||||
let mut bits = RV_CONTEXT_NO_DEFAULT_CALL | RV_CONTEXT_ARGUMENTS_VOID_PTR;
|
||||
if self.context_stack_trace() {
|
||||
bits |= RV_CONTEXT_STACK_TRACE;
|
||||
}
|
||||
bits
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
let flags = FeatureFlags::default();
|
||||
assert_eq!(
|
||||
flags.as_bits(),
|
||||
RV_CONTEXT_NO_DEFAULT_CALL | RV_CONTEXT_ARGUMENTS_VOID_PTR
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn just_stack_trace() {
|
||||
let mut flags = FeatureFlags::default();
|
||||
flags.set_context_stack_trace(true);
|
||||
assert_eq!(
|
||||
flags.as_bits(),
|
||||
RV_CONTEXT_NO_DEFAULT_CALL | RV_CONTEXT_STACK_TRACE | RV_CONTEXT_ARGUMENTS_VOID_PTR
|
||||
);
|
||||
}
|
||||
}
|
||||
123
vendor/arma-rs/src/group.rs
vendored
Normal file
123
vendor/arma-rs/src/group.rs
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
command::{fn_handler, Factory, Handler},
|
||||
context::{Context, GroupContext},
|
||||
State,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
/// A group of commands.
|
||||
/// Called from Arma using `[group]:[command]`.
|
||||
pub struct Group {
|
||||
commands: HashMap<String, Box<Handler>>,
|
||||
children: HashMap<String, Self>,
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
#[must_use]
|
||||
/// Creates a new group
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
commands: HashMap::new(),
|
||||
children: HashMap::new(),
|
||||
state: State::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
/// Add a new state value to the group if it has not be added already
|
||||
pub fn state<T>(self, state: T) -> Self
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
self.state.set(state);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
/// Freeze the group's state, preventing the state from changing, allowing for faster reads
|
||||
pub fn freeze_state(mut self) -> Self {
|
||||
self.state.freeze();
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
/// Add a command to the group
|
||||
pub fn command<S, F, I, R>(mut self, name: S, handler: F) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
F: Factory<I, R> + 'static,
|
||||
{
|
||||
self.commands
|
||||
.insert(name.into(), Box::new(fn_handler(handler)));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
/// Add a group to the group
|
||||
pub fn group<S>(mut self, name: S, child: Self) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.children.insert(name.into(), child);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct InternalGroup {
|
||||
commands: HashMap<String, Box<Handler>>,
|
||||
children: HashMap<String, Self>,
|
||||
pub(crate) state: Arc<State>,
|
||||
}
|
||||
|
||||
impl InternalGroup {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn handle(
|
||||
&self,
|
||||
context: Context,
|
||||
acm: &crate::ArmaContextManager,
|
||||
function: &str,
|
||||
output: *mut libc::c_char,
|
||||
size: libc::size_t,
|
||||
args: Option<*mut *mut i8>,
|
||||
count: Option<libc::c_int>,
|
||||
) -> libc::c_int {
|
||||
if let Some((group, function)) = function.split_once(':') {
|
||||
self.children.get(group).map_or(1, |group| {
|
||||
group.handle(context, acm, function, output, size, args, count)
|
||||
})
|
||||
} else if let Some(handler) = self.commands.get(function) {
|
||||
(handler.handler)(
|
||||
context.with_group(GroupContext::new(self.state.clone())),
|
||||
acm,
|
||||
output,
|
||||
size,
|
||||
args,
|
||||
count,
|
||||
)
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Group> for InternalGroup {
|
||||
fn from(group: Group) -> Self {
|
||||
let children = group
|
||||
.children
|
||||
.into_iter()
|
||||
.map(|(name, group)| (name, Self::from(group)))
|
||||
.collect();
|
||||
Self {
|
||||
commands: group.commands,
|
||||
children,
|
||||
state: Arc::new(group.state),
|
||||
}
|
||||
}
|
||||
}
|
||||
520
vendor/arma-rs/src/lib.rs
vendored
Normal file
520
vendor/arma-rs/src/lib.rs
vendored
Normal file
@@ -0,0 +1,520 @@
|
||||
#![warn(missing_docs, nonstandard_style)]
|
||||
#![doc = include_str!(concat!(env!("OUT_DIR"), "/README.md"))]
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
pub use arma_rs_proc::{arma, FromArma, IntoArma};
|
||||
|
||||
#[cfg(feature = "extension")]
|
||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||
#[cfg(feature = "extension")]
|
||||
pub use libc;
|
||||
|
||||
#[cfg(all(target_os = "windows", target_arch = "x86"))]
|
||||
pub use link_args;
|
||||
|
||||
#[cfg(feature = "extension")]
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
mod flags;
|
||||
|
||||
mod value;
|
||||
pub use value::{loadout, DirectReturn, FromArma, FromArmaError, IntoArma, Value};
|
||||
|
||||
#[cfg(feature = "extension")]
|
||||
mod call_context;
|
||||
#[cfg(feature = "extension")]
|
||||
use call_context::{ArmaCallContext, ArmaContextManager};
|
||||
#[cfg(feature = "extension")]
|
||||
pub use call_context::{CallContext, CallContextStackTrace, Caller, Mission, Server, Source};
|
||||
#[cfg(feature = "extension")]
|
||||
mod ext_result;
|
||||
#[cfg(feature = "extension")]
|
||||
pub use ext_result::IntoExtResult;
|
||||
#[cfg(feature = "extension")]
|
||||
mod command;
|
||||
#[cfg(feature = "extension")]
|
||||
pub use command::*;
|
||||
#[cfg(feature = "extension")]
|
||||
pub mod context;
|
||||
#[cfg(feature = "extension")]
|
||||
pub use context::*;
|
||||
#[cfg(feature = "extension")]
|
||||
mod group;
|
||||
#[cfg(feature = "extension")]
|
||||
pub use group::Group;
|
||||
#[cfg(feature = "extension")]
|
||||
pub mod testing;
|
||||
#[cfg(feature = "extension")]
|
||||
pub use testing::Result;
|
||||
|
||||
#[cfg(all(windows, feature = "extension"))]
|
||||
#[doc(hidden)]
|
||||
/// Used by generated code to call back into Arma
|
||||
pub type Callback = extern "stdcall" fn(
|
||||
*const libc::c_char,
|
||||
*const libc::c_char,
|
||||
*const libc::c_char,
|
||||
) -> libc::c_int;
|
||||
#[cfg(all(not(windows), feature = "extension"))]
|
||||
#[doc(hidden)]
|
||||
/// Used by generated code to call back into Arma
|
||||
pub type Callback =
|
||||
extern "C" fn(*const libc::c_char, *const libc::c_char, *const libc::c_char) -> libc::c_int;
|
||||
/// Requests a call context from Arma
|
||||
pub type ContextRequest = unsafe extern "C" fn();
|
||||
|
||||
#[cfg(feature = "extension")]
|
||||
enum CallbackMessage {
|
||||
Call(String, String, Option<Value>),
|
||||
Terminate,
|
||||
}
|
||||
|
||||
#[cfg(feature = "extension")]
|
||||
/// State TypeMap that can hold at most one value per type key.
|
||||
pub type State = state::TypeMap![Send + Sync];
|
||||
|
||||
#[cfg(windows)]
|
||||
/// Allows a console to be allocated for the extension.
|
||||
static CONSOLE_ALLOCATED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_upper_case_globals, reason = "This is a C API")]
|
||||
/// Feature flags read on each callExtension call.
|
||||
pub static mut RVExtensionFeatureFlags: u64 = flags::RV_CONTEXT_NO_DEFAULT_CALL;
|
||||
|
||||
/// Contains all the information about your extension
|
||||
/// This is used by the generated code to interface with Arma
|
||||
#[cfg(feature = "extension")]
|
||||
pub struct Extension {
|
||||
version: String,
|
||||
group: group::InternalGroup,
|
||||
allow_no_args: bool,
|
||||
callback: Option<Callback>,
|
||||
callback_channel: (Sender<CallbackMessage>, Receiver<CallbackMessage>),
|
||||
callback_thread: Option<std::thread::JoinHandle<()>>,
|
||||
context_manager: Rc<ArmaContextManager>,
|
||||
pre218_clear_context_override: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "extension")]
|
||||
impl Extension {
|
||||
#[must_use]
|
||||
/// Creates a new extension.
|
||||
pub fn build() -> ExtensionBuilder {
|
||||
ExtensionBuilder {
|
||||
version: String::from("0.0.0"),
|
||||
group: Group::new(),
|
||||
allow_no_args: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "extension")]
|
||||
impl Extension {
|
||||
#[must_use]
|
||||
/// Returns the version of the extension.
|
||||
pub fn version(&self) -> &str {
|
||||
&self.version
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Returns if the extension can be called without any arguments.
|
||||
/// Example:
|
||||
/// ```sqf
|
||||
/// "my_ext" callExtension "my_func"
|
||||
/// ```
|
||||
pub const fn allow_no_args(&self) -> bool {
|
||||
self.allow_no_args
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Called by generated code, do not call directly.
|
||||
pub fn register_callback(&mut self, callback: Callback) {
|
||||
self.callback = Some(callback);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Called by generated code, do not call directly.
|
||||
/// # Safety
|
||||
/// This function is unsafe because it interacts with the C API.
|
||||
pub unsafe fn handle_call_context(&mut self, args: *mut *mut i8, count: libc::c_int) {
|
||||
self.context_manager
|
||||
.replace(Some(ArmaCallContext::from_arma(args, count)));
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Get a context for interacting with Arma
|
||||
pub fn context(&self) -> Context {
|
||||
Context::new(
|
||||
self.callback_channel.0.clone(),
|
||||
GlobalContext::new(self.version.clone(), self.group.state.clone()),
|
||||
GroupContext::new(self.group.state.clone()),
|
||||
)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Called by generated code, do not call directly.
|
||||
/// # Safety
|
||||
/// This function is unsafe because it interacts with the C API.
|
||||
pub unsafe fn handle_call(
|
||||
&self,
|
||||
function: *mut libc::c_char,
|
||||
output: *mut libc::c_char,
|
||||
size: libc::size_t,
|
||||
args: Option<*mut *mut i8>,
|
||||
count: Option<libc::c_int>,
|
||||
clear_call_context: bool,
|
||||
) -> libc::c_int {
|
||||
if clear_call_context && !self.pre218_clear_context_override {
|
||||
self.context_manager.replace(None);
|
||||
}
|
||||
let function = if let Ok(cstring) = std::ffi::CStr::from_ptr(function).to_str() {
|
||||
cstring.to_string()
|
||||
} else {
|
||||
return 1;
|
||||
};
|
||||
match function.as_str() {
|
||||
#[cfg(windows)]
|
||||
"::console" => {
|
||||
if !CONSOLE_ALLOCATED.swap(true, std::sync::atomic::Ordering::SeqCst) {
|
||||
let _ = windows::Win32::System::Console::AllocConsole();
|
||||
}
|
||||
0
|
||||
}
|
||||
_ => self.group.handle(
|
||||
self.context().with_buffer_size(size),
|
||||
self.context_manager.as_ref(),
|
||||
&function,
|
||||
output,
|
||||
size,
|
||||
args,
|
||||
count,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Create a version of the extension that can be used in tests.
|
||||
pub fn testing(self) -> testing::Extension {
|
||||
testing::Extension::new(self)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Called by generated code, do not call directly.
|
||||
pub fn run_callbacks(&mut self) {
|
||||
let callback = self.callback;
|
||||
let (_, rx) = self.callback_channel.clone();
|
||||
self.callback_thread = Some(std::thread::spawn(move || {
|
||||
while let Ok(CallbackMessage::Call(name, func, data)) = rx.recv() {
|
||||
if let Some(c) = callback {
|
||||
let name = if let Ok(cstring) = std::ffi::CString::new(name) {
|
||||
cstring
|
||||
} else {
|
||||
error!("callback name was not valid");
|
||||
continue;
|
||||
};
|
||||
let func = if let Ok(cstring) = std::ffi::CString::new(func) {
|
||||
cstring
|
||||
} else {
|
||||
error!("callback func was not valid");
|
||||
continue;
|
||||
};
|
||||
let data = if let Ok(cstring) = std::ffi::CString::new(match data {
|
||||
Some(value) => match value {
|
||||
Value::String(s) => s,
|
||||
v => v.to_string(),
|
||||
},
|
||||
None => String::new(),
|
||||
}) {
|
||||
cstring
|
||||
} else {
|
||||
error!("callback data was not valid");
|
||||
continue;
|
||||
};
|
||||
|
||||
let (name, func, data) = (name.into_raw(), func.into_raw(), data.into_raw());
|
||||
loop {
|
||||
if c(name, func, data) >= 0 {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
unsafe {
|
||||
drop(std::ffi::CString::from_raw(name));
|
||||
drop(std::ffi::CString::from_raw(func));
|
||||
drop(std::ffi::CString::from_raw(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "extension")]
|
||||
impl Drop for Extension {
|
||||
// Never called when loaded by arma, instead this is purely required for rust testing.
|
||||
fn drop(&mut self) {
|
||||
if let Some(thread) = self.callback_thread.take() {
|
||||
let (tx, _) = &self.callback_channel;
|
||||
tx.send(CallbackMessage::Terminate).unwrap();
|
||||
thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to build an extension.
|
||||
#[cfg(feature = "extension")]
|
||||
pub struct ExtensionBuilder {
|
||||
version: String,
|
||||
group: Group,
|
||||
allow_no_args: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "extension")]
|
||||
impl ExtensionBuilder {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
/// Sets the version of the extension.
|
||||
pub fn version(mut self, version: String) -> Self {
|
||||
self.version = version;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
/// Add a group to the extension.
|
||||
pub fn group<S>(mut self, name: S, group: Group) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.group = self.group.group(name.into(), group);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
/// Add a new state value to the extension if it has not be added already
|
||||
pub fn state<T>(mut self, state: T) -> Self
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
self.group = self.group.state(state);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
/// Freeze the extension's state, preventing the state from changing, allowing for faster reads
|
||||
pub fn freeze_state(mut self) -> Self {
|
||||
self.group = self.group.freeze_state();
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
/// Allows the extension to be called without any arguments.
|
||||
/// Example:
|
||||
/// ```sqf
|
||||
/// "my_ext" callExtension "my_func"
|
||||
/// ```
|
||||
pub const fn allow_no_args(mut self) -> Self {
|
||||
self.allow_no_args = true;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
/// Add a command to the extension.
|
||||
pub fn command<S, F, I, R>(mut self, name: S, handler: F) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
F: Factory<I, R> + 'static,
|
||||
{
|
||||
self.group = self.group.command(name, handler);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
/// Builds the extension.
|
||||
pub fn finish(self) -> Extension {
|
||||
#[expect(unused_mut, reason = "Only used on Windows release")]
|
||||
let mut pre218 = false;
|
||||
#[allow(unused_variables)]
|
||||
let function_name =
|
||||
std::ffi::CString::new("RVExtensionRequestContext").expect("CString::new failed");
|
||||
#[cfg(all(windows, not(debug_assertions)))]
|
||||
let request_context: ContextRequest = {
|
||||
let handle = unsafe { winapi::um::libloaderapi::GetModuleHandleW(std::ptr::null()) };
|
||||
if handle.is_null() {
|
||||
panic!("GetModuleHandleW failed");
|
||||
}
|
||||
let func_address =
|
||||
unsafe { winapi::um::libloaderapi::GetProcAddress(handle, function_name.as_ptr()) };
|
||||
if func_address.is_null() {
|
||||
pre218 = true;
|
||||
empty_request_context
|
||||
} else {
|
||||
unsafe { std::mem::transmute(func_address) }
|
||||
}
|
||||
};
|
||||
#[cfg(all(not(windows), not(debug_assertions)))]
|
||||
let request_context: ContextRequest = {
|
||||
let handle = unsafe { libc::dlopen(std::ptr::null(), libc::RTLD_LAZY) };
|
||||
if handle.is_null() {
|
||||
panic!("Failed to open handle to current process");
|
||||
}
|
||||
let func_address = unsafe { libc::dlsym(handle, function_name.as_ptr()) };
|
||||
if func_address.is_null() {
|
||||
pre218 = true;
|
||||
empty_request_context
|
||||
} else {
|
||||
let func = unsafe { std::mem::transmute(func_address) };
|
||||
unsafe { libc::dlclose(handle) };
|
||||
func
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let request_context = empty_request_context;
|
||||
|
||||
Extension {
|
||||
version: self.version,
|
||||
group: self.group.into(),
|
||||
allow_no_args: self.allow_no_args,
|
||||
callback: None,
|
||||
callback_channel: unbounded(),
|
||||
callback_thread: None,
|
||||
context_manager: Rc::new(ArmaContextManager::new(request_context)),
|
||||
pre218_clear_context_override: pre218,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn empty_request_context() {}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Called by generated code, do not call directly.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it interacts with the C API.
|
||||
///
|
||||
/// # Note
|
||||
/// This function assumes `buf_size` includes space for a single terminating zero byte at the end.
|
||||
#[cfg(feature = "extension")]
|
||||
pub unsafe fn write_cstr(
|
||||
string: String,
|
||||
ptr: *mut libc::c_char,
|
||||
buf_size: libc::size_t,
|
||||
) -> Option<libc::size_t> {
|
||||
if string.is_empty() {
|
||||
return Some(0);
|
||||
}
|
||||
|
||||
let cstr = std::ffi::CString::new(string).ok()?;
|
||||
let len_to_copy = cstr.as_bytes().len();
|
||||
if len_to_copy >= buf_size {
|
||||
return None;
|
||||
}
|
||||
|
||||
ptr.copy_from(cstr.as_ptr(), len_to_copy);
|
||||
ptr.add(len_to_copy).write(0x00);
|
||||
Some(len_to_copy)
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "extension"))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn write_size_zero() {
|
||||
const BUF_SIZE: libc::size_t = 0;
|
||||
let mut buf = [0; BUF_SIZE];
|
||||
let result = unsafe { write_cstr("a".to_string(), buf.as_mut_ptr(), BUF_SIZE) };
|
||||
|
||||
assert_eq!(result, None);
|
||||
assert_eq!(buf, [0; BUF_SIZE]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_size_zero_empty() {
|
||||
const BUF_SIZE: libc::size_t = 0;
|
||||
let mut buf = [0; BUF_SIZE];
|
||||
let result = unsafe { write_cstr("".to_string(), buf.as_mut_ptr(), BUF_SIZE) };
|
||||
|
||||
assert_eq!(result, Some(0));
|
||||
assert_eq!(buf, [0; BUF_SIZE]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_size_one() {
|
||||
const BUF_SIZE: libc::size_t = 1;
|
||||
let mut buf = [0; BUF_SIZE];
|
||||
let result = unsafe { write_cstr("a".to_string(), buf.as_mut_ptr(), BUF_SIZE) };
|
||||
|
||||
assert_eq!(result, None);
|
||||
assert_eq!(buf, [0; BUF_SIZE]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_size_one_empty() {
|
||||
const BUF_SIZE: libc::size_t = 1;
|
||||
let mut buf = [0; BUF_SIZE];
|
||||
let result = unsafe { write_cstr("".to_string(), buf.as_mut_ptr(), BUF_SIZE) };
|
||||
|
||||
assert_eq!(result, Some(0));
|
||||
assert_eq!(buf, [0; BUF_SIZE]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_empty() {
|
||||
const BUF_SIZE: libc::size_t = 7;
|
||||
let mut buf = [0; BUF_SIZE];
|
||||
let result = unsafe { write_cstr("".to_string(), buf.as_mut_ptr(), BUF_SIZE) };
|
||||
|
||||
assert_eq!(result, Some(0));
|
||||
assert_eq!(buf, [0; BUF_SIZE]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_half() {
|
||||
const BUF_SIZE: libc::size_t = 7;
|
||||
let mut buf = [0; BUF_SIZE];
|
||||
let result = unsafe { write_cstr("foo".to_string(), buf.as_mut_ptr(), BUF_SIZE) };
|
||||
|
||||
assert_eq!(result, Some(3));
|
||||
assert_eq!(buf, (b"foo\0\0\0\0").map(|c| c as i8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_full() {
|
||||
const BUF_SIZE: libc::size_t = 7;
|
||||
let mut buf = [0; BUF_SIZE];
|
||||
let result = unsafe { write_cstr("foobar".to_string(), buf.as_mut_ptr(), BUF_SIZE) };
|
||||
|
||||
assert_eq!(result, Some(6));
|
||||
assert_eq!(buf, (b"foobar\0").map(|c| c as i8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_overflow() {
|
||||
const BUF_SIZE: libc::size_t = 7;
|
||||
let mut buf = [0; BUF_SIZE];
|
||||
let result = unsafe { write_cstr("foo bar".to_string(), buf.as_mut_ptr(), BUF_SIZE) };
|
||||
|
||||
assert_eq!(result, None);
|
||||
assert_eq!(buf, [0; BUF_SIZE]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_overwrite() {
|
||||
const BUF_SIZE: libc::size_t = 7;
|
||||
let mut buf = (b"zzzzzz\0").map(|c| c as i8);
|
||||
let result = unsafe { write_cstr("a".to_string(), buf.as_mut_ptr(), BUF_SIZE) };
|
||||
|
||||
assert_eq!(result, Some(1));
|
||||
assert_eq!(buf, (b"a\0zzzz\0").map(|c| c as i8));
|
||||
}
|
||||
}
|
||||
167
vendor/arma-rs/src/testing.rs
vendored
Normal file
167
vendor/arma-rs/src/testing.rs
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
//! For testing your extension.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{CallbackMessage, Context, State, Value};
|
||||
|
||||
use crate::{ArmaCallContext, Caller, Mission, Server, Source};
|
||||
|
||||
/// Wrapper around [`crate::Extension`] used for testing.
|
||||
pub struct Extension(crate::Extension);
|
||||
|
||||
const BUFFER_SIZE: libc::size_t = 10240; // The sized used by Arma 3 as of 2021-12-30
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
/// Result of an event handler
|
||||
pub enum Result<T, E> {
|
||||
/// an event has been handled and the handler is done, the value of T is the return value of the event handler
|
||||
Ok(T),
|
||||
/// the handler has encountered an error, the value of T is the return value of the event handler
|
||||
Err(E),
|
||||
/// an event is handled but the handler is not done and should receive another event
|
||||
Continue,
|
||||
/// the handler reached the specified timeout
|
||||
Timeout,
|
||||
}
|
||||
|
||||
impl<T, E> Result<T, E> {
|
||||
/// Returns true if the result is an ok result
|
||||
pub fn is_ok(&self) -> bool {
|
||||
matches!(self, Self::Ok(_))
|
||||
}
|
||||
|
||||
/// Returns true if the result is an error
|
||||
pub fn is_err(&self) -> bool {
|
||||
matches!(self, Self::Err(_))
|
||||
}
|
||||
|
||||
/// Returns true if the result is a continue result
|
||||
pub fn is_continue(&self) -> bool {
|
||||
matches!(self, Self::Continue)
|
||||
}
|
||||
|
||||
/// Returns true if the result is a timeout result
|
||||
pub fn is_timeout(&self) -> bool {
|
||||
matches!(self, Self::Timeout)
|
||||
}
|
||||
}
|
||||
|
||||
impl Extension {
|
||||
/// Create a new testing Extension
|
||||
pub fn new(ext: crate::Extension) -> Self {
|
||||
Self(ext)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Returns a context for simulating interactions with Arma
|
||||
pub fn context(&self) -> Context {
|
||||
self.0.context().with_buffer_size(BUFFER_SIZE)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Get a reference to the extensions state container
|
||||
pub fn state(&self) -> &State {
|
||||
&self.0.group.state
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
/// Call a function with Arma call context.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it interacts with the C API.
|
||||
pub fn call_with_context(
|
||||
&self,
|
||||
function: &str,
|
||||
args: Option<Vec<String>>,
|
||||
caller: Caller,
|
||||
source: Source,
|
||||
mission: Mission,
|
||||
server: Server,
|
||||
remote_exec_owner: i16,
|
||||
) -> (String, libc::c_int) {
|
||||
self.0.context_manager.replace(Some(ArmaCallContext::new(
|
||||
caller,
|
||||
source,
|
||||
mission,
|
||||
server,
|
||||
remote_exec_owner,
|
||||
)));
|
||||
unsafe { self.handle_call(function, args) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Call a function without Arma call context.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it interacts with the C API.
|
||||
///
|
||||
/// # Note
|
||||
/// If the `call-context` feature is enabled, this function passes default values for each field.
|
||||
pub fn call(&self, function: &str, args: Option<Vec<String>>) -> (String, libc::c_int) {
|
||||
self.0.context_manager.replace(None);
|
||||
unsafe { self.handle_call(function, args) }
|
||||
}
|
||||
|
||||
unsafe fn handle_call(
|
||||
&self,
|
||||
function: &str,
|
||||
args: Option<Vec<String>>,
|
||||
) -> (String, libc::c_int) {
|
||||
let mut output = [0; BUFFER_SIZE];
|
||||
let len = args.as_ref().map(|a| a.len().try_into().unwrap());
|
||||
let mut args_pointer = args.map(|v| {
|
||||
v.into_iter()
|
||||
.map(|s| std::ffi::CString::new(s).unwrap().into_raw())
|
||||
.collect::<Vec<*mut i8>>()
|
||||
});
|
||||
let res = self.0.group.handle(
|
||||
self.context(),
|
||||
&self.0.context_manager,
|
||||
function,
|
||||
output.as_mut_ptr(),
|
||||
BUFFER_SIZE,
|
||||
args_pointer.as_mut().map(Vec::as_mut_ptr),
|
||||
len,
|
||||
);
|
||||
if let Some(args) = args_pointer {
|
||||
for arg in args {
|
||||
let _ = std::ffi::CString::from_raw(arg);
|
||||
}
|
||||
}
|
||||
(
|
||||
std::ffi::CStr::from_ptr(output.as_ptr())
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
res,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a callback handler
|
||||
///
|
||||
/// Returns a Result from the handler if the callback was handled,
|
||||
/// or `Result::Timeout` if either no event was received, or the handler
|
||||
/// returned `Result::Continue` until the timeout was reached.
|
||||
///
|
||||
/// The handler must return a Result indicating the callback was handled to exit
|
||||
/// `Result::Continue` will continue to provide events to the handler until another variant is returned
|
||||
pub fn callback_handler<F, T, E>(&self, handler: F, timeout: Duration) -> Result<T, E>
|
||||
where
|
||||
F: Fn(&str, &str, Option<Value>) -> Result<T, E>,
|
||||
{
|
||||
let (_, rx) = &self.0.callback_channel;
|
||||
let deadline = std::time::Instant::now() + timeout;
|
||||
loop {
|
||||
match rx.recv_deadline(deadline) {
|
||||
Ok(CallbackMessage::Call(name, func, data)) => match handler(&name, &func, data) {
|
||||
Result::Ok(value) => return Result::Ok(value),
|
||||
Result::Err(error) => return Result::Err(error),
|
||||
Result::Timeout => return Result::Timeout,
|
||||
Result::Continue => {}
|
||||
},
|
||||
_ => return Result::Timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
vendor/arma-rs/src/value/features/chrono.rs
vendored
Normal file
41
vendor/arma-rs/src/value/features/chrono.rs
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, TimeZone, Timelike};
|
||||
|
||||
use crate::{FromArma, FromArmaError, IntoArma, Value};
|
||||
|
||||
impl IntoArma for NaiveDateTime {
|
||||
fn to_arma(&self) -> Value {
|
||||
vec![
|
||||
self.year() as u32,
|
||||
self.month(),
|
||||
self.day(),
|
||||
self.hour(),
|
||||
self.minute(),
|
||||
self.second(),
|
||||
self.nanosecond() / 1_000_000,
|
||||
]
|
||||
.to_arma()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TimeZone> IntoArma for DateTime<T> {
|
||||
fn to_arma(&self) -> Value {
|
||||
self.naive_utc().to_arma()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArma for NaiveDateTime {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let arma_date: [i64; 7] = FromArma::from_arma(s)?;
|
||||
Ok(NaiveDate::from_ymd(
|
||||
arma_date[0].try_into().unwrap(),
|
||||
arma_date[1].try_into().unwrap(),
|
||||
arma_date[2].try_into().unwrap(),
|
||||
)
|
||||
.and_hms_milli(
|
||||
arma_date[3].try_into().unwrap(),
|
||||
arma_date[4].try_into().unwrap(),
|
||||
arma_date[5].try_into().unwrap(),
|
||||
arma_date[6].try_into().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
8
vendor/arma-rs/src/value/features/mod.rs
vendored
Normal file
8
vendor/arma-rs/src/value/features/mod.rs
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
#[cfg(feature = "uuid")]
|
||||
mod uuid;
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
mod chrono;
|
||||
|
||||
#[cfg(feature = "serde_json")]
|
||||
mod serde_json;
|
||||
51
vendor/arma-rs/src/value/features/serde_json.rs
vendored
Normal file
51
vendor/arma-rs/src/value/features/serde_json.rs
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
use crate::{FromArma, FromArmaError, IntoArma, Value};
|
||||
|
||||
impl IntoArma for serde_json::Value {
|
||||
fn to_arma(&self) -> Value {
|
||||
match self {
|
||||
serde_json::Value::Null => Value::Null,
|
||||
serde_json::Value::Bool(b) => Value::Boolean(*b),
|
||||
serde_json::Value::Number(n) => Value::Number(if n.is_f64() {
|
||||
n.as_f64().unwrap()
|
||||
} else if n.is_i64() {
|
||||
n.as_i64().unwrap() as f64
|
||||
} else if n.is_u64() {
|
||||
n.as_u64().unwrap() as f64
|
||||
} else {
|
||||
unreachable!()
|
||||
}),
|
||||
serde_json::Value::String(s) => Value::String(s.to_owned()),
|
||||
serde_json::Value::Array(v) => {
|
||||
Value::Array(v.iter().map(|v| v.to_arma()).collect::<Vec<Value>>())
|
||||
}
|
||||
serde_json::Value::Object(o) => o
|
||||
.iter()
|
||||
.map(|(k, v)| vec![Value::String(k.to_owned()), v.to_arma()])
|
||||
.collect::<Vec<Vec<Value>>>()
|
||||
.to_arma(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArma for serde_json::Value {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let value = Value::from_arma(s)?;
|
||||
Ok(value.to_json())
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// Convert a Value to a serde_json::Value
|
||||
pub fn to_json(&self) -> serde_json::Value {
|
||||
match self {
|
||||
Value::Null => serde_json::Value::Null,
|
||||
Value::Boolean(b) => serde_json::Value::Bool(*b),
|
||||
Value::Number(n) => {
|
||||
serde_json::Value::Number(serde_json::Number::from_f64(*n).unwrap())
|
||||
}
|
||||
Value::String(s) => serde_json::Value::String(s.to_owned()),
|
||||
Value::Array(v) => serde_json::Value::Array(v.iter().map(|v| v.to_json()).collect()),
|
||||
Value::Unknown(s) => serde_json::Value::String(s.to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
17
vendor/arma-rs/src/value/features/uuid.rs
vendored
Normal file
17
vendor/arma-rs/src/value/features/uuid.rs
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
use crate::{FromArma, FromArmaError, IntoArma, Value};
|
||||
|
||||
impl IntoArma for uuid::Uuid {
|
||||
fn to_arma(&self) -> Value {
|
||||
self.to_string().to_arma()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArma for uuid::Uuid {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let s = s
|
||||
.strip_prefix('"')
|
||||
.and_then(|s| s.strip_suffix('"'))
|
||||
.unwrap_or(&s);
|
||||
uuid::Uuid::parse_str(s).map_err(FromArmaError::custom)
|
||||
}
|
||||
}
|
||||
558
vendor/arma-rs/src/value/from_arma.rs
vendored
Normal file
558
vendor/arma-rs/src/value/from_arma.rs
vendored
Normal file
@@ -0,0 +1,558 @@
|
||||
use crate::Value;
|
||||
|
||||
fn split_array(s: &str) -> Vec<String> {
|
||||
let mut nest = 0;
|
||||
let mut parts = Vec::new();
|
||||
let mut part = String::new();
|
||||
for c in s.chars() {
|
||||
if c == '[' {
|
||||
part.push(c);
|
||||
nest += 1;
|
||||
} else if c == ']' {
|
||||
nest -= 1;
|
||||
part.push(c);
|
||||
} else if c == ',' && nest == 0 {
|
||||
parts.push(part.trim().to_string());
|
||||
part = String::new();
|
||||
} else {
|
||||
part.push(c);
|
||||
}
|
||||
}
|
||||
let part = part.trim().to_string();
|
||||
if !part.is_empty() {
|
||||
parts.push(part);
|
||||
}
|
||||
parts
|
||||
}
|
||||
|
||||
/// Error type for [`FromArma`]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum FromArmaError {
|
||||
/// Invalid [`crate::Value`]
|
||||
InvalidValue(String),
|
||||
/// Invalid primitive value
|
||||
InvalidPrimitive(String),
|
||||
/// Collection size mismatch
|
||||
InvalidLength {
|
||||
/// Expected size
|
||||
expected: usize,
|
||||
/// Actual size
|
||||
actual: usize,
|
||||
},
|
||||
|
||||
/// Missing opening(true) or closing(false) bracket
|
||||
MissingBracket(bool),
|
||||
/// Missing field
|
||||
MissingField(String),
|
||||
/// Unknown field
|
||||
UnknownField(String),
|
||||
/// Duplicate field
|
||||
DuplicateField(String),
|
||||
|
||||
/// Custom error message
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FromArmaError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidValue(s) => write!(f, "invalid value: {s}"),
|
||||
Self::InvalidPrimitive(s) => write!(f, "error parsing primitive: {s}"),
|
||||
Self::InvalidLength { expected, actual } => {
|
||||
write!(f, "expected {expected} elements, got {actual}")
|
||||
}
|
||||
Self::MissingBracket(start) => match *start {
|
||||
true => write!(f, "missing '[' at start of array"),
|
||||
false => write!(f, "missing ']' at end of array"),
|
||||
},
|
||||
Self::MissingField(s) => write!(f, "missing field: {s}"),
|
||||
Self::UnknownField(s) => write!(f, "unknown field: {s}"),
|
||||
Self::DuplicateField(s) => write!(f, "duplicate field: {s}"),
|
||||
Self::Custom(s) => f.write_str(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArmaError {
|
||||
/// Creates a new [`FromArmaError::Custom`]
|
||||
pub fn custom(msg: impl std::fmt::Display) -> Self {
|
||||
Self::Custom(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for converting a value from Arma to a Rust value.
|
||||
pub trait FromArma: Sized {
|
||||
/// Converts a value from Arma to a Rust value.
|
||||
/// # Errors
|
||||
/// Will return an error if the value cannot be converted.
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError>;
|
||||
}
|
||||
|
||||
#[cfg(not(any(test, doc, debug_assertions)))]
|
||||
impl FromArma for String {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let Some(s) = s.strip_prefix('"').and_then(|s| s.strip_suffix('"')) else {
|
||||
return Err(FromArmaError::InvalidPrimitive(String::from(
|
||||
"missing '\"' at start or end of string",
|
||||
)));
|
||||
};
|
||||
Ok(s.replace("\"\"", "\""))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, doc, debug_assertions))]
|
||||
impl FromArma for String {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let s = s
|
||||
.strip_prefix('"')
|
||||
.and_then(|s| s.strip_suffix('"'))
|
||||
.unwrap_or(&s);
|
||||
Ok(s.replace("\"\"", "\""))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from_arma {
|
||||
($($t:ty),*) => {
|
||||
$(
|
||||
impl FromArma for $t {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let s = s.strip_suffix('"').and_then(|s| s.strip_prefix('"')).unwrap_or(&s);
|
||||
s.parse::<Self>().map_err(|e| FromArmaError::InvalidPrimitive(e.to_string()))
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
impl_from_arma!(f32, f64, bool, char);
|
||||
|
||||
macro_rules! impl_from_arma_number {
|
||||
($($t:ty),*) => {
|
||||
$(
|
||||
impl FromArma for $t {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let s = s.strip_suffix('"').and_then(|s| s.strip_prefix('"')).unwrap_or(&s);
|
||||
if s.contains("e") {
|
||||
// parse exponential notation
|
||||
let mut parts = s.split('e');
|
||||
let base = match parts.next().unwrap() {
|
||||
s if !s.is_empty() => s.parse::<f64>().map_err(|e| FromArmaError::InvalidPrimitive(e.to_string()))?,
|
||||
_ => return Err(FromArmaError::InvalidPrimitive("invalid number literal".to_string())),
|
||||
};
|
||||
let exp = match parts.next().unwrap() {
|
||||
s if !s.is_empty() => s.parse::<i32>().map_err(|e| FromArmaError::InvalidPrimitive(e.to_string()))?,
|
||||
_ => return Err(FromArmaError::InvalidPrimitive("invalid number literal".to_string())),
|
||||
};
|
||||
return Ok((base * 10.0_f64.powi(exp)) as $t);
|
||||
}
|
||||
s.parse::<Self>().map_err(|e| FromArmaError::InvalidPrimitive(e.to_string()))
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
impl_from_arma_number!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
|
||||
|
||||
macro_rules! impl_from_arma_tuple {
|
||||
{ $c: expr, $($t:ident)* } => {
|
||||
impl<$($t),*> FromArma for ($($t),*)
|
||||
where
|
||||
$($t: FromArma),*
|
||||
{
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let v: [Value; $c] = FromArma::from_arma(s)?;
|
||||
let mut iter = v.iter();
|
||||
Ok((
|
||||
$($t::from_arma(iter.next().unwrap().to_string())?),*
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_from_arma_tuple! { 2, A B }
|
||||
impl_from_arma_tuple! { 3, A B C }
|
||||
impl_from_arma_tuple! { 4, A B C D }
|
||||
impl_from_arma_tuple! { 5, A B C D E }
|
||||
impl_from_arma_tuple! { 6, A B C D E F }
|
||||
impl_from_arma_tuple! { 7, A B C D E F G }
|
||||
impl_from_arma_tuple! { 8, A B C D E F G H }
|
||||
impl_from_arma_tuple! { 9, A B C D E F G H I }
|
||||
impl_from_arma_tuple! { 10, A B C D E F G H I J }
|
||||
impl_from_arma_tuple! { 11, A B C D E F G H I J K }
|
||||
impl_from_arma_tuple! { 12, A B C D E F G H I J K L }
|
||||
impl_from_arma_tuple! { 13, A B C D E F G H I J K L M }
|
||||
impl_from_arma_tuple! { 14, A B C D E F G H I J K L M N }
|
||||
impl_from_arma_tuple! { 15, A B C D E F G H I J K L M N O }
|
||||
impl_from_arma_tuple! { 16, A B C D E F G H I J K L M N O P }
|
||||
impl_from_arma_tuple! { 17, A B C D E F G H I J K L M N O P Q }
|
||||
impl_from_arma_tuple! { 18, A B C D E F G H I J K L M N O P Q R }
|
||||
impl_from_arma_tuple! { 19, A B C D E F G H I J K L M N O P Q R S }
|
||||
impl_from_arma_tuple! { 20, A B C D E F G H I J K L M N O P Q R S T }
|
||||
impl_from_arma_tuple! { 21, A B C D E F G H I J K L M N O P Q R S T U }
|
||||
impl_from_arma_tuple! { 22, A B C D E F G H I J K L M N O P Q R S T U V }
|
||||
impl_from_arma_tuple! { 23, A B C D E F G H I J K L M N O P Q R S T U V W }
|
||||
impl_from_arma_tuple! { 24, A B C D E F G H I J K L M N O P Q R S T U V W X }
|
||||
impl_from_arma_tuple! { 25, A B C D E F G H I J K L M N O P Q R S T U V W X Y }
|
||||
impl_from_arma_tuple! { 26, A B C D E F G H I J K L M N O P Q R S T U V W X Y Z }
|
||||
|
||||
impl<T> FromArma for Vec<T>
|
||||
where
|
||||
T: FromArma,
|
||||
{
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let source = s
|
||||
.strip_prefix('[')
|
||||
.ok_or(FromArmaError::MissingBracket(true))?
|
||||
.strip_suffix(']')
|
||||
.ok_or(FromArmaError::MissingBracket(false))?;
|
||||
let parts = split_array(source);
|
||||
parts.iter().try_fold(Self::new(), |mut acc, p| {
|
||||
acc.push(T::from_arma(p.to_string())?);
|
||||
Ok(acc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> FromArma for [T; N]
|
||||
where
|
||||
T: FromArma,
|
||||
{
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let v: Vec<T> = FromArma::from_arma(s)?;
|
||||
let len = v.len();
|
||||
v.try_into().map_err(|_| FromArmaError::InvalidLength {
|
||||
expected: N,
|
||||
actual: len,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, S> FromArma for std::collections::HashMap<K, V, S>
|
||||
where
|
||||
K: FromArma + Eq + std::hash::Hash,
|
||||
V: FromArma,
|
||||
S: std::hash::BuildHasher + Default,
|
||||
{
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let data: Vec<(K, V)> = FromArma::from_arma(s)?;
|
||||
let mut ret = Self::default();
|
||||
for (k, v) in data {
|
||||
ret.insert(k, v);
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Value;
|
||||
|
||||
#[test]
|
||||
fn parse_tuple_varying_types() {
|
||||
assert_eq!(
|
||||
(String::from("hello"), 123),
|
||||
<(String, i32)>::from_arma(r#"["hello", 123]"#.to_string()).unwrap()
|
||||
);
|
||||
assert!(<(String, String)>::from_arma(r#"["hello", 123, "world"]"#.to_string()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_tuple_size_errors() {
|
||||
assert_eq!(
|
||||
<(String, i32)>::from_arma(r#"[]"#.to_string()),
|
||||
Err(FromArmaError::InvalidLength {
|
||||
expected: 2,
|
||||
actual: 0
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
<(String, i32)>::from_arma(r#"["hello"]"#.to_string()),
|
||||
Err(FromArmaError::InvalidLength {
|
||||
expected: 2,
|
||||
actual: 1
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
<(String, i32)>::from_arma(r#"["hello", 123, 456]"#.to_string()),
|
||||
Err(FromArmaError::InvalidLength {
|
||||
expected: 2,
|
||||
actual: 3
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_tuple_bracket_errors() {
|
||||
assert_eq!(
|
||||
<(String, i32)>::from_arma(r#"["hello", 123"#.to_string()),
|
||||
Err(FromArmaError::MissingBracket(false))
|
||||
);
|
||||
assert_eq!(
|
||||
<(String, i32)>::from_arma(r#""hello", 123"#.to_string()),
|
||||
Err(FromArmaError::MissingBracket(true))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_2() {
|
||||
assert_eq!(
|
||||
(0, 1),
|
||||
<(u8, u8)>::from_arma(r#"[0, 1]"#.to_string()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_3() {
|
||||
assert_eq!(
|
||||
(0, 1, 2),
|
||||
<(u8, u8, u8)>::from_arma(r#"[0, 1, 2]"#.to_string()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_4() {
|
||||
assert_eq!(
|
||||
(0, 1, 2, 3),
|
||||
<(u8, u8, u8, u8)>::from_arma(r#"[0, 1, 2, 3]"#.to_string()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_5() {
|
||||
assert_eq!(
|
||||
(0, 1, 2, 3, 4),
|
||||
<(u8, u8, u8, u8, u8)>::from_arma(r#"[0, 1, 2, 3, 4]"#.to_string()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_6() {
|
||||
assert_eq!(
|
||||
(0, 1, 2, 3, 4, 5),
|
||||
<(u8, u8, u8, u8, u8, u8)>::from_arma(r#"[0, 1, 2, 3, 4, 5]"#.to_string()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_7() {
|
||||
assert_eq!(
|
||||
(0, 1, 2, 3, 4, 5, 6),
|
||||
<(u8, u8, u8, u8, u8, u8, u8)>::from_arma(r#"[0, 1, 2, 3, 4, 5, 6]"#.to_string())
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_8() {
|
||||
assert_eq!(
|
||||
(0, 1, 2, 3, 4, 5, 6, 7),
|
||||
<(u8, u8, u8, u8, u8, u8, u8, u8)>::from_arma(
|
||||
r#"[0, 1, 2, 3, 4, 5, 6, 7]"#.to_string()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_9() {
|
||||
assert_eq!(
|
||||
(0, 1, 2, 3, 4, 5, 6, 7, 8),
|
||||
<(u8, u8, u8, u8, u8, u8, u8, u8, u8)>::from_arma(
|
||||
r#"[0, 1, 2, 3, 4, 5, 6, 7, 8]"#.to_string()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_10() {
|
||||
assert_eq!(
|
||||
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
|
||||
<(u8, u8, u8, u8, u8, u8, u8, u8, u8, u8)>::from_arma(
|
||||
r#"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"#.to_string()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_string() {
|
||||
assert_eq!(
|
||||
String::from("hello"),
|
||||
<String>::from_arma(r#""hello""#.to_string()).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
String::from("\"hello\""),
|
||||
<String>::from_arma(r#"""hello"""#.to_string()).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
String::from(r#"hello "john"."#),
|
||||
<String>::from_arma(r#""hello ""john"".""#.to_string()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_vec() {
|
||||
assert_eq!(
|
||||
vec![String::from("hello"), String::from("bye"),],
|
||||
<Vec<String>>::from_arma(r#"["hello","bye"]"#.to_string()).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
vec![String::from("hello"), String::from("world")],
|
||||
<Vec<String>>::from_arma(r#"[hello, "world"]"#.to_string()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_vec_bracket_errors() {
|
||||
assert_eq!(
|
||||
<Vec<String>>::from_arma(r#""hello","bye"]"#.to_string()),
|
||||
Err(FromArmaError::MissingBracket(true))
|
||||
);
|
||||
assert_eq!(
|
||||
<Vec<String>>::from_arma(r#"["hello","bye""#.to_string()),
|
||||
Err(FromArmaError::MissingBracket(false))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_vec_tuple() {
|
||||
assert_eq!(
|
||||
(vec![(String::from("hello"), 123), (String::from("bye"), 321),]),
|
||||
<Vec<(String, i32)>>::from_arma(r#"[["hello", 123],["bye", 321]]"#.to_string())
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_slice() {
|
||||
assert_eq!(
|
||||
vec![String::from("hello"), String::from("bye"),],
|
||||
<[String; 2]>::from_arma(r#"["hello","bye"]"#.to_string()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_slice_size_errors() {
|
||||
assert_eq!(
|
||||
<[String; 2]>::from_arma(r#"[]"#.to_string()),
|
||||
Err(FromArmaError::InvalidLength {
|
||||
expected: 2,
|
||||
actual: 0
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
<[String; 2]>::from_arma(r#"["hello"]"#.to_string()),
|
||||
Err(FromArmaError::InvalidLength {
|
||||
expected: 2,
|
||||
actual: 1
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
<[String; 2]>::from_arma(r#"["hello","bye","world"]"#.to_string()),
|
||||
Err(FromArmaError::InvalidLength {
|
||||
expected: 2,
|
||||
actual: 3
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_hashmap() {
|
||||
assert_eq!(
|
||||
std::collections::HashMap::from([
|
||||
(String::from("hello"), 123),
|
||||
(String::from("bye"), 321),
|
||||
]),
|
||||
<std::collections::HashMap<String, i32>>::from_arma(
|
||||
r#"[["hello", 123],["bye",321]]"#.to_string()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
std::collections::HashMap::from([
|
||||
(String::from("hello"), 123),
|
||||
(String::from("bye"), 321),
|
||||
(String::from("hello"), 321),
|
||||
]),
|
||||
<std::collections::HashMap<String, i32>>::from_arma(
|
||||
r#"[["hello", 123],["bye",321],["hello", 321]]"#.to_string()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_exponential() {
|
||||
assert_eq!(1.0e-10, <f64>::from_arma(r#"1.0e-10"#.to_string()).unwrap());
|
||||
assert_eq!(
|
||||
1_227_700,
|
||||
<u32>::from_arma(r#"1.2277e+006"#.to_string()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_exponential_errors() {
|
||||
assert_eq!(
|
||||
<f64>::from_arma(r#"e-10"#.to_string()),
|
||||
Err(FromArmaError::InvalidPrimitive(
|
||||
"invalid float literal".to_string()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
<f64>::from_arma(r#"1.0e"#.to_string()),
|
||||
Err(FromArmaError::InvalidPrimitive(
|
||||
"invalid float literal".to_string()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
<u32>::from_arma(r#"e-10"#.to_string()),
|
||||
Err(FromArmaError::InvalidPrimitive(
|
||||
"invalid number literal".to_string()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
<u32>::from_arma(r#"1.0e"#.to_string()),
|
||||
Err(FromArmaError::InvalidPrimitive(
|
||||
"invalid number literal".to_string()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_value_tuple() {
|
||||
assert_eq!(
|
||||
(
|
||||
Value::String(String::from("hello")),
|
||||
Value::String(String::from("world"))
|
||||
),
|
||||
<(Value, Value)>::from_arma(r#"["hello", "world"]"#.to_string()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_value_vec() {
|
||||
assert_eq!(
|
||||
vec![
|
||||
Value::String(String::from("hello")),
|
||||
Value::String(String::from("world"))
|
||||
],
|
||||
<Vec<Value>>::from_arma(r#"["hello", "world"]"#.to_string()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_float() {
|
||||
assert_eq!(1.0, <f64>::from_arma(r#"1.0"#.to_string()).unwrap());
|
||||
assert_eq!(1.0, <f64>::from_arma(r#"1"#.to_string()).unwrap());
|
||||
assert_eq!(1.0, <f64>::from_arma(r#"1.0e+0"#.to_string()).unwrap());
|
||||
assert_eq!(-1.0, <f64>::from_arma(r#"-1.0"#.to_string()).unwrap());
|
||||
assert_eq!(1.0, <f64>::from_arma(r#""1.0""#.to_string()).unwrap());
|
||||
assert_eq!(1.0, <f64>::from_arma(r#""1""#.to_string()).unwrap());
|
||||
assert_eq!(1.0, <f64>::from_arma(r#""1.0e+0""#.to_string()).unwrap());
|
||||
assert_eq!(-1.0, <f64>::from_arma(r#""-1.0""#.to_string()).unwrap());
|
||||
}
|
||||
}
|
||||
506
vendor/arma-rs/src/value/into_arma.rs
vendored
Normal file
506
vendor/arma-rs/src/value/into_arma.rs
vendored
Normal file
@@ -0,0 +1,506 @@
|
||||
use super::Value;
|
||||
|
||||
/// Convert a type to a value that can be sent into Arma
|
||||
pub trait IntoArma {
|
||||
/// Convert a type to a value that can be sent into Arma
|
||||
fn to_arma(&self) -> Value;
|
||||
}
|
||||
|
||||
pub struct DirectReturn(Value);
|
||||
impl Value {
|
||||
/// A workaround to return a value directly to Arma
|
||||
pub fn direct(value: Value) -> DirectReturn {
|
||||
DirectReturn(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoArma for DirectReturn {
|
||||
fn to_arma(&self) -> Value {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoArma for () {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Null
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_into_arma_tuple {
|
||||
{ $c: expr, $($t:ident)* } => {
|
||||
seq_macro::seq!(N in 0..$c {
|
||||
impl<$($t),*> IntoArma for ($($t),*)
|
||||
where
|
||||
$($t: IntoArma),*
|
||||
{
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Array(vec![
|
||||
#(
|
||||
self.N.to_arma(),
|
||||
)*
|
||||
])
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
impl_into_arma_tuple! { 2, A B }
|
||||
impl_into_arma_tuple! { 3, A B C }
|
||||
impl_into_arma_tuple! { 4, A B C D }
|
||||
impl_into_arma_tuple! { 5, A B C D E }
|
||||
impl_into_arma_tuple! { 6, A B C D E F }
|
||||
impl_into_arma_tuple! { 7, A B C D E F G }
|
||||
impl_into_arma_tuple! { 8, A B C D E F G H }
|
||||
impl_into_arma_tuple! { 9, A B C D E F G H I }
|
||||
impl_into_arma_tuple! { 10, A B C D E F G H I J }
|
||||
impl_into_arma_tuple! { 11, A B C D E F G H I J K }
|
||||
impl_into_arma_tuple! { 12, A B C D E F G H I J K L }
|
||||
impl_into_arma_tuple! { 13, A B C D E F G H I J K L M }
|
||||
impl_into_arma_tuple! { 14, A B C D E F G H I J K L M N }
|
||||
impl_into_arma_tuple! { 15, A B C D E F G H I J K L M N O }
|
||||
impl_into_arma_tuple! { 16, A B C D E F G H I J K L M N O P }
|
||||
impl_into_arma_tuple! { 17, A B C D E F G H I J K L M N O P Q }
|
||||
impl_into_arma_tuple! { 18, A B C D E F G H I J K L M N O P Q R }
|
||||
impl_into_arma_tuple! { 19, A B C D E F G H I J K L M N O P Q R S }
|
||||
impl_into_arma_tuple! { 20, A B C D E F G H I J K L M N O P Q R S T }
|
||||
impl_into_arma_tuple! { 21, A B C D E F G H I J K L M N O P Q R S T U }
|
||||
impl_into_arma_tuple! { 22, A B C D E F G H I J K L M N O P Q R S T U V }
|
||||
impl_into_arma_tuple! { 23, A B C D E F G H I J K L M N O P Q R S T U V W }
|
||||
impl_into_arma_tuple! { 24, A B C D E F G H I J K L M N O P Q R S T U V W X }
|
||||
impl_into_arma_tuple! { 25, A B C D E F G H I J K L M N O P Q R S T U V W X Y }
|
||||
impl_into_arma_tuple! { 26, A B C D E F G H I J K L M N O P Q R S T U V W X Y Z }
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_tuples() {
|
||||
let a = (1, "two");
|
||||
assert_eq!(
|
||||
Value::Array(vec![Value::Number(1.0), Value::String("two".to_string())]),
|
||||
a.to_arma()
|
||||
);
|
||||
}
|
||||
|
||||
impl IntoArma for Vec<Value> {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Array(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoArma for Vec<T>
|
||||
where
|
||||
T: IntoArma,
|
||||
{
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Array(self.iter().map(IntoArma::to_arma).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_vec() {
|
||||
assert_eq!(String::from("[1,2,3]"), vec![1, 2, 3].to_arma().to_string());
|
||||
assert_eq!(
|
||||
String::from(r#"["hello","world"]"#),
|
||||
vec!["hello", "world"].to_arma().to_string()
|
||||
);
|
||||
}
|
||||
|
||||
impl<T> IntoArma for &[T]
|
||||
where
|
||||
T: IntoArma,
|
||||
{
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Array(self.iter().map(IntoArma::to_arma).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_slice() {
|
||||
assert_eq!(
|
||||
String::from("[1,2,3]"),
|
||||
vec![1, 2, 3].as_slice().to_arma().to_string()
|
||||
)
|
||||
}
|
||||
|
||||
impl IntoArma for String {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::String(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_string() {
|
||||
assert_eq!(
|
||||
String::from("\"hello\""),
|
||||
String::from("hello").to_arma().to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
String::from(r#""hello ""john"".""#),
|
||||
String::from(r#"hello "john"."#).to_arma().to_string()
|
||||
);
|
||||
}
|
||||
|
||||
impl IntoArma for &'static str {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::String((*self).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_static_str() {
|
||||
assert_eq!(String::from("\"hello\""), "hello".to_arma().to_string())
|
||||
}
|
||||
|
||||
impl IntoArma for bool {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Boolean(*self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_bool() {
|
||||
assert_eq!(String::from("true"), true.to_arma().to_string())
|
||||
}
|
||||
|
||||
impl IntoArma for i8 {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Number(f64::from(self.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_i8() {
|
||||
assert_eq!(String::from("1"), 1i8.to_arma().to_string())
|
||||
}
|
||||
|
||||
impl IntoArma for i16 {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Number(f64::from(self.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_i16() {
|
||||
assert_eq!(String::from("1"), 1i16.to_arma().to_string())
|
||||
}
|
||||
|
||||
impl IntoArma for i32 {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Number(f64::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_i32() {
|
||||
assert_eq!(String::from("1"), 1i32.to_arma().to_string())
|
||||
}
|
||||
|
||||
impl IntoArma for f32 {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Number(f64::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_f32() {
|
||||
assert_eq!(String::from("1"), 1f32.to_arma().to_string());
|
||||
assert_eq!(String::from("1.5"), 1.5f32.to_arma().to_string());
|
||||
}
|
||||
|
||||
impl IntoArma for f64 {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Number(*self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_f64() {
|
||||
assert_eq!(String::from("1"), 1f64.to_arma().to_string());
|
||||
assert_eq!(String::from("1.5"), 1.5f64.to_arma().to_string());
|
||||
}
|
||||
|
||||
impl IntoArma for u8 {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Number(f64::from(self.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_u8() {
|
||||
assert_eq!(String::from("1"), 1u8.to_arma().to_string())
|
||||
}
|
||||
|
||||
impl IntoArma for u16 {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Number(f64::from(self.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_u16() {
|
||||
assert_eq!(String::from("1"), 1u16.to_arma().to_string())
|
||||
}
|
||||
|
||||
impl IntoArma for u32 {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Number(f64::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_u32() {
|
||||
assert_eq!(String::from("1"), 1u32.to_arma().to_string())
|
||||
}
|
||||
|
||||
impl<T: IntoArma> IntoArma for Option<T> {
|
||||
fn to_arma(&self) -> Value {
|
||||
match self {
|
||||
Some(v) => v.to_arma(),
|
||||
None => Value::Null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_option() {
|
||||
assert_eq!(String::from("null"), None::<i32>.to_arma().to_string());
|
||||
assert_eq!(String::from("1"), Some(1).to_arma().to_string());
|
||||
}
|
||||
|
||||
impl<K, V, S> IntoArma for std::collections::HashMap<K, V, S>
|
||||
where
|
||||
K: IntoArma,
|
||||
V: IntoArma,
|
||||
S: std::hash::BuildHasher,
|
||||
{
|
||||
fn to_arma(&self) -> Value {
|
||||
self.iter()
|
||||
.map(|(k, v)| vec![k.to_arma(), v.to_arma()])
|
||||
.collect::<Vec<Vec<Value>>>()
|
||||
.to_arma()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, S> IntoArma for std::collections::HashMap<K, Value, S>
|
||||
where
|
||||
K: IntoArma,
|
||||
S: std::hash::BuildHasher,
|
||||
{
|
||||
fn to_arma(&self) -> Value {
|
||||
self.iter()
|
||||
.map(|(k, v)| vec![k.to_arma(), v.clone()])
|
||||
.collect::<Vec<Vec<Value>>>()
|
||||
.to_arma()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_hashmap() {
|
||||
use std::collections::HashMap;
|
||||
{
|
||||
let mut map = HashMap::new();
|
||||
map.insert("key".to_string(), "value".to_string());
|
||||
let map = map.to_arma();
|
||||
assert_eq!(map.to_string(), r#"[["key","value"]]"#.to_string());
|
||||
}
|
||||
{
|
||||
let mut map = HashMap::new();
|
||||
map.insert("key1".to_string(), "value1".to_string());
|
||||
map.insert("key2".to_string(), "value2".to_string());
|
||||
let map = map.to_arma().to_string();
|
||||
assert!(
|
||||
map == r#"[["key1","value1"],["key2","value2"]]"#
|
||||
|| map == r#"[["key2","value2"],["key1","value1"]]"#
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
#[must_use]
|
||||
/// Returns an Option representing if the value is null
|
||||
pub const fn as_null(&self) -> Option<()> {
|
||||
match self {
|
||||
Self::Null => Some(()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Checks if the value is a null variant
|
||||
pub const fn is_null(&self) -> bool {
|
||||
self.as_null().is_some()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Returns an Option representing if the value is a number
|
||||
pub const fn as_f64(&self) -> Option<f64> {
|
||||
match *self {
|
||||
Self::Number(n) => Some(n),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Checks if the value is a number
|
||||
pub const fn is_number(&self) -> bool {
|
||||
self.as_f64().is_some()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Returns an Option representing if the value is an array
|
||||
pub const fn as_vec(&self) -> Option<&Vec<Self>> {
|
||||
match *self {
|
||||
Self::Array(ref vec) => Some(vec),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Checks if the value is an array
|
||||
pub const fn is_array(&self) -> bool {
|
||||
self.as_vec().is_some()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Returns an Option representing if the value is a boolean
|
||||
pub const fn as_bool(&self) -> Option<bool> {
|
||||
match *self {
|
||||
Self::Boolean(bool) => Some(bool),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Checks if the value is a boolean
|
||||
pub const fn is_boolean(&self) -> bool {
|
||||
self.as_bool().is_some()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Returns an Option representing if the value is a string
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
match *self {
|
||||
Self::String(ref string) => Some(string),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Checks if the value is a string
|
||||
pub fn is_string(&self) -> bool {
|
||||
self.as_str().is_some()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Checks if the value is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Self::Null => true,
|
||||
Self::Number(n) => *n == 0.0,
|
||||
Self::Array(a) => a.is_empty(),
|
||||
Self::Boolean(b) => !*b,
|
||||
Self::String(s) => s.is_empty(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_null() {
|
||||
assert!(Value::Null.is_null());
|
||||
assert!(!Value::Boolean(false).is_null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_number() {
|
||||
assert!(Value::Number(54.0).is_number());
|
||||
assert!(!Value::Boolean(false).is_number());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_array() {
|
||||
assert!(Value::Array(Vec::new()).is_array());
|
||||
assert!(!Value::Boolean(false).is_array());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_boolean() {
|
||||
assert!(Value::Boolean(false).is_boolean());
|
||||
assert!(!Value::Number(54.0).is_boolean());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_string() {
|
||||
assert!(Value::String(String::new()).is_string());
|
||||
assert!(!Value::Boolean(false).is_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_nil() {
|
||||
assert!(Value::Null.as_null().is_some())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_f32() {
|
||||
let v = Value::Number(54.0).as_f64().unwrap();
|
||||
assert!((54.0 - v) == 0.0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_vec() {
|
||||
let array = Value::Array(vec![Value::String("hello".into())]);
|
||||
assert_eq!(array.to_string(), r#"["hello"]"#.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_bool() {
|
||||
assert!(Value::Boolean(true).as_bool().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_str() {
|
||||
let v = Value::String("hello world".into());
|
||||
let s = v.as_str().unwrap();
|
||||
assert_eq!(s, "hello world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_empty() {
|
||||
assert!(Value::String("".into()).is_empty());
|
||||
assert!(Value::Array(vec![]).is_empty());
|
||||
assert!(Value::Boolean(false).is_empty());
|
||||
assert!(Value::String(String::new()).is_empty());
|
||||
assert!(Value::Number(0.0).is_empty());
|
||||
assert!(Value::Null.is_empty());
|
||||
|
||||
assert!(!Value::String("test".into()).is_empty());
|
||||
assert!(!Value::Array(vec![Value::Boolean(false)]).is_empty());
|
||||
assert!(!Value::Boolean(true).is_empty());
|
||||
assert!(!Value::Number(55.0).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_array() {
|
||||
let array = Value::Array(vec![]);
|
||||
assert_eq!(array.to_string(), r#"[]"#.to_string());
|
||||
}
|
||||
}
|
||||
200
vendor/arma-rs/src/value/loadout/assigned.rs
vendored
Normal file
200
vendor/arma-rs/src/value/loadout/assigned.rs
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
use crate::{FromArma, FromArmaError, IntoArma, Value};
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
/// Assigned items in the loadout
|
||||
pub struct AssignedItems(String, String, String, String, String, String);
|
||||
impl AssignedItems {
|
||||
/// The class name of the assigned map
|
||||
#[must_use]
|
||||
pub fn map(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Set the class name of the assigned map
|
||||
pub fn set_map(&mut self, map: String) {
|
||||
self.0 = map;
|
||||
}
|
||||
|
||||
/// The class name of the assigned terminal
|
||||
#[must_use]
|
||||
pub fn terminal(&self) -> &str {
|
||||
&self.1
|
||||
}
|
||||
|
||||
/// Set the class name of the assigned terminal
|
||||
pub fn set_terminal(&mut self, terminal: String) {
|
||||
self.1 = terminal;
|
||||
}
|
||||
|
||||
/// The class name of the assigned radio
|
||||
#[must_use]
|
||||
pub fn radio(&self) -> &str {
|
||||
&self.2
|
||||
}
|
||||
|
||||
/// Set the class name of the assigned radio
|
||||
pub fn set_radio(&mut self, radio: String) {
|
||||
self.2 = radio;
|
||||
}
|
||||
|
||||
/// The class name of the assigned compass
|
||||
#[must_use]
|
||||
pub fn compass(&self) -> &str {
|
||||
&self.3
|
||||
}
|
||||
|
||||
/// Set the class name of the assigned compass
|
||||
pub fn set_compass(&mut self, compass: String) {
|
||||
self.3 = compass;
|
||||
}
|
||||
|
||||
/// The class name of the assigned watch
|
||||
#[must_use]
|
||||
pub fn watch(&self) -> &str {
|
||||
&self.4
|
||||
}
|
||||
|
||||
/// Set the class name of the assigned watch
|
||||
pub fn set_watch(&mut self, watch: String) {
|
||||
self.4 = watch;
|
||||
}
|
||||
|
||||
/// The class name of the assigned NVG
|
||||
#[must_use]
|
||||
pub fn nvg(&self) -> &str {
|
||||
&self.5
|
||||
}
|
||||
|
||||
/// Set the class name of the assigned NVG
|
||||
pub fn set_nvg(&mut self, nvg: String) {
|
||||
self.5 = nvg;
|
||||
}
|
||||
|
||||
/// Get all items
|
||||
#[must_use]
|
||||
pub fn classes(&self) -> [&str; 6] {
|
||||
[
|
||||
self.map(),
|
||||
self.terminal(),
|
||||
self.radio(),
|
||||
self.compass(),
|
||||
self.watch(),
|
||||
self.nvg(),
|
||||
]
|
||||
}
|
||||
|
||||
#[deprecated(note = "Use `classes` instead")]
|
||||
#[must_use]
|
||||
/// Get all items
|
||||
pub fn items(&self) -> [&str; 6] {
|
||||
self.classes()
|
||||
}
|
||||
}
|
||||
impl FromArma for AssignedItems {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
<(String, String, String, String, String, String)>::from_arma(s).map(
|
||||
|(map, gps, radio, compass, watch, nvg)| Self(map, gps, radio, compass, watch, nvg),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl IntoArma for AssignedItems {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Array(vec![
|
||||
Value::String(self.map().to_owned()),
|
||||
Value::String(self.terminal().to_owned()),
|
||||
Value::String(self.radio().to_owned()),
|
||||
Value::String(self.compass().to_owned()),
|
||||
Value::String(self.watch().to_owned()),
|
||||
Value::String(self.nvg().to_owned()),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::AssignedItems;
|
||||
use crate::{FromArma, IntoArma, Value};
|
||||
|
||||
#[test]
|
||||
fn map() {
|
||||
let mut assigned = AssignedItems::default();
|
||||
assert_eq!(assigned.map(), "");
|
||||
assigned.set_map("map".to_owned());
|
||||
assert_eq!(assigned.map(), "map");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal() {
|
||||
let mut assigned = AssignedItems::default();
|
||||
assert_eq!(assigned.terminal(), "");
|
||||
assigned.set_terminal("terminal".to_owned());
|
||||
assert_eq!(assigned.terminal(), "terminal");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn radio() {
|
||||
let mut assigned = AssignedItems::default();
|
||||
assert_eq!(assigned.radio(), "");
|
||||
assigned.set_radio("radio".to_owned());
|
||||
assert_eq!(assigned.radio(), "radio");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compass() {
|
||||
let mut assigned = AssignedItems::default();
|
||||
assert_eq!(assigned.compass(), "");
|
||||
assigned.set_compass("compass".to_owned());
|
||||
assert_eq!(assigned.compass(), "compass");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn watch() {
|
||||
let mut assigned = AssignedItems::default();
|
||||
assert_eq!(assigned.watch(), "");
|
||||
assigned.set_watch("watch".to_owned());
|
||||
assert_eq!(assigned.watch(), "watch");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nvg() {
|
||||
let mut assigned = AssignedItems::default();
|
||||
assert_eq!(assigned.nvg(), "");
|
||||
assigned.set_nvg("nvg".to_owned());
|
||||
assert_eq!(assigned.nvg(), "nvg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_arma() {
|
||||
let s = "[\"map\",\"terminal\",\"radio\",\"compass\",\"watch\",\"nvg\"]";
|
||||
let assigned = AssignedItems::from_arma(s.to_owned()).unwrap();
|
||||
assert_eq!(assigned.map(), "map");
|
||||
assert_eq!(assigned.terminal(), "terminal");
|
||||
assert_eq!(assigned.radio(), "radio");
|
||||
assert_eq!(assigned.compass(), "compass");
|
||||
assert_eq!(assigned.watch(), "watch");
|
||||
assert_eq!(assigned.nvg(), "nvg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_arma() {
|
||||
let mut assigned = AssignedItems::default();
|
||||
assigned.set_map("map".to_owned());
|
||||
assigned.set_terminal("terminal".to_owned());
|
||||
assigned.set_radio("radio".to_owned());
|
||||
assigned.set_compass("compass".to_owned());
|
||||
assigned.set_watch("watch".to_owned());
|
||||
assigned.set_nvg("nvg".to_owned());
|
||||
let s = assigned.to_arma();
|
||||
assert_eq!(
|
||||
s,
|
||||
Value::Array(vec![
|
||||
Value::String("map".to_owned()),
|
||||
Value::String("terminal".to_owned()),
|
||||
Value::String("radio".to_owned()),
|
||||
Value::String("compass".to_owned()),
|
||||
Value::String("watch".to_owned()),
|
||||
Value::String("nvg".to_owned()),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
183
vendor/arma-rs/src/value/loadout/container.rs
vendored
Normal file
183
vendor/arma-rs/src/value/loadout/container.rs
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
use crate::{FromArma, FromArmaError, IntoArma, Value};
|
||||
|
||||
use super::InventoryItem;
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
/// A uniform, vest, or backpack
|
||||
pub struct Container(Option<(String, Vec<InventoryItem>)>);
|
||||
impl Container {
|
||||
/// Create a new container
|
||||
#[must_use]
|
||||
pub const fn new(class: String) -> Self {
|
||||
Self(Some((class, vec![])))
|
||||
}
|
||||
|
||||
/// The container exists
|
||||
#[must_use]
|
||||
pub const fn exists(&self) -> bool {
|
||||
self.0.is_some()
|
||||
}
|
||||
|
||||
/// The class name of the container
|
||||
#[must_use]
|
||||
pub fn class(&self) -> Option<&str> {
|
||||
self.0.as_ref().map(|(class, _)| class.as_str())
|
||||
}
|
||||
|
||||
/// Set the class name of the container
|
||||
pub fn set_class(&mut self, class: String) {
|
||||
if let Some(container) = self.0.as_mut() {
|
||||
container.0 = class;
|
||||
} else {
|
||||
self.0 = Some((class, vec![]));
|
||||
}
|
||||
}
|
||||
|
||||
/// The items in the container
|
||||
#[must_use]
|
||||
pub fn items(&self) -> Option<&Vec<InventoryItem>> {
|
||||
self.0.as_ref().map(|(_, items)| items)
|
||||
}
|
||||
|
||||
/// The items in the container
|
||||
pub fn items_mut(&mut self) -> Option<&mut Vec<InventoryItem>> {
|
||||
self.0.as_mut().map(|(_, items)| items)
|
||||
}
|
||||
|
||||
/// Get all classes and their quantities, including the container itself
|
||||
#[must_use]
|
||||
pub fn classes(&self) -> Vec<(String, u32)> {
|
||||
let mut classes = vec![];
|
||||
if let Some((class, items)) = &self.0 {
|
||||
classes.push((class.clone(), 1));
|
||||
for item in items {
|
||||
classes.push((item.class().to_string(), item.count()));
|
||||
}
|
||||
}
|
||||
classes
|
||||
}
|
||||
}
|
||||
impl FromArma for Container {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
if s == "[]" {
|
||||
return Ok(Self(None));
|
||||
}
|
||||
<(String, Vec<InventoryItem>)>::from_arma(s).map(|(name, items)| Self(Some((name, items))))
|
||||
}
|
||||
}
|
||||
impl IntoArma for Container {
|
||||
fn to_arma(&self) -> Value {
|
||||
self.0.as_ref().map_or_else(
|
||||
|| Value::Array(vec![]),
|
||||
|container| {
|
||||
Value::Array(vec![
|
||||
Value::String(container.0.clone()),
|
||||
Value::Array(container.1.iter().map(IntoArma::to_arma).collect()),
|
||||
])
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{loadout::InventoryItem, FromArma, IntoArma, Value};
|
||||
|
||||
use super::Container;
|
||||
|
||||
#[test]
|
||||
fn exists() {
|
||||
let container = Container::default();
|
||||
assert!(!container.exists());
|
||||
let container = Container::new("container".to_string());
|
||||
assert!(container.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class() {
|
||||
let container = Container::default();
|
||||
assert!(container.class().is_none());
|
||||
let mut container = Container::new("container".to_string());
|
||||
assert_eq!(container.class().unwrap(), "container");
|
||||
container.set_class("container2".to_string());
|
||||
assert_eq!(container.class().unwrap(), "container2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn items() {
|
||||
let container = Container::default();
|
||||
assert!(container.items().is_none());
|
||||
let mut container = Container::new("container".to_string());
|
||||
assert!(container.items().is_some());
|
||||
let items = vec![
|
||||
InventoryItem::new_item("item1".to_string(), 1),
|
||||
InventoryItem::new_item("item2".to_string(), 2),
|
||||
];
|
||||
container.0.as_mut().unwrap().1 = items.clone();
|
||||
assert_eq!(container.items().unwrap(), &items);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_arma() {
|
||||
let container = Container::from_arma("[]".to_string()).unwrap();
|
||||
assert!(!container.exists());
|
||||
let container =
|
||||
Container::from_arma("[\"container\",[[\"item1\",1],[\"item2\",2]]]".to_string())
|
||||
.unwrap();
|
||||
assert!(container.exists());
|
||||
assert_eq!(container.class().unwrap(), "container");
|
||||
assert_eq!(
|
||||
container.items().unwrap(),
|
||||
&vec![
|
||||
InventoryItem::new_item("item1".to_string(), 1),
|
||||
InventoryItem::new_item("item2".to_string(), 2),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_arma() {
|
||||
let container = Container::default();
|
||||
assert_eq!(container.to_arma(), Value::Array(vec![]));
|
||||
let mut container = Container::new("container".to_string());
|
||||
assert_eq!(
|
||||
container.to_arma(),
|
||||
Value::Array(vec![
|
||||
Value::String("container".to_string()),
|
||||
Value::Array(vec![]),
|
||||
])
|
||||
);
|
||||
let items = vec![
|
||||
InventoryItem::new_item("item1".to_string(), 1),
|
||||
InventoryItem::new_item("item2".to_string(), 2),
|
||||
];
|
||||
container.0.as_mut().unwrap().1 = items;
|
||||
assert_eq!(
|
||||
container.to_arma(),
|
||||
Value::Array(vec![
|
||||
Value::String("container".to_string()),
|
||||
Value::Array(vec![
|
||||
Value::Array(vec![Value::String("item1".to_string()), Value::Number(1.0),]),
|
||||
Value::Array(vec![Value::String("item2".to_string()), Value::Number(2.0),]),
|
||||
]),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classes() {
|
||||
let container = Container::from_arma("[]".to_string()).unwrap();
|
||||
assert!(!container.exists());
|
||||
let container =
|
||||
Container::from_arma("[\"container\",[[\"item1\",1],[\"item2\",2]]]".to_string())
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
container.classes(),
|
||||
vec![
|
||||
("container".to_string(), 1),
|
||||
("item1".to_string(), 1),
|
||||
("item2".to_string(), 2)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
79
vendor/arma-rs/src/value/loadout/extended.rs
vendored
Normal file
79
vendor/arma-rs/src/value/loadout/extended.rs
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{FromArma, FromArmaError, Value};
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
/// CBA Extended Loadout
|
||||
///
|
||||
/// <https://github.com/CBATeam/CBA_A3/pull/1503>
|
||||
pub struct CBAExtended(Option<HashMap<String, Value>>);
|
||||
|
||||
impl CBAExtended {
|
||||
/// Create a new CBA Extended Loadout Array
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// The map has no data or does not exist
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_none() || self.0.as_ref().unwrap().is_empty()
|
||||
}
|
||||
|
||||
/// Get a value from the map
|
||||
pub fn get(&self, key: &str) -> Option<&Value> {
|
||||
self.0.as_ref().and_then(|map| map.get(key))
|
||||
}
|
||||
|
||||
/// Get a mutable value from the map
|
||||
pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
|
||||
self.0.as_mut().and_then(|map| map.get_mut(key))
|
||||
}
|
||||
|
||||
/// Insert a value into the map
|
||||
pub fn insert(&mut self, key: String, value: Value) -> Option<Value> {
|
||||
self.0.get_or_insert_with(HashMap::new).insert(key, value)
|
||||
}
|
||||
|
||||
/// Remove a value from the map
|
||||
pub fn remove(&mut self, key: &str) -> Option<Value> {
|
||||
self.0.as_mut().and_then(|map| map.remove(key))
|
||||
}
|
||||
|
||||
/// Iterate over the keys in the map
|
||||
pub fn values(&self) -> impl Iterator<Item = &Value> {
|
||||
self.0.iter().flat_map(|map| map.values())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArma for CBAExtended {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let value = <Vec<(String, Value)>>::from_arma(s);
|
||||
match value {
|
||||
Ok(value) => Ok(Self(Some(value.into_iter().collect()))),
|
||||
Err(_) => Ok(Self(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_from_arma() {
|
||||
let value = CBAExtended::from_arma("[]".to_string());
|
||||
assert!(value.is_ok());
|
||||
assert_eq!(value.unwrap(), CBAExtended(Some(HashMap::new())));
|
||||
|
||||
let value = CBAExtended::from_arma("[[\"cba_xeh_enabled\",true]]".to_string());
|
||||
assert!(value.is_ok());
|
||||
assert_eq!(
|
||||
value.unwrap(),
|
||||
CBAExtended(Some(
|
||||
vec![("cba_xeh_enabled".to_string(), Value::Boolean(true))]
|
||||
.into_iter()
|
||||
.collect()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
187
vendor/arma-rs/src/value/loadout/inventory_item.rs
vendored
Normal file
187
vendor/arma-rs/src/value/loadout/inventory_item.rs
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
use crate::{FromArma, FromArmaError, IntoArma, Value};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// An item stored in a uniform, vest, or backpack
|
||||
pub enum InventoryItem {
|
||||
/// An item that is not a magazine
|
||||
Item(String, u32),
|
||||
/// A magazine
|
||||
Magazine(String, u32, u32),
|
||||
}
|
||||
impl InventoryItem {
|
||||
/// Create a new item
|
||||
#[must_use]
|
||||
pub const fn new_item(class: String, count: u32) -> Self {
|
||||
Self::Item(class, count)
|
||||
}
|
||||
|
||||
/// Create a new magazine
|
||||
#[must_use]
|
||||
pub const fn new_magazine(class: String, count: u32, ammo: u32) -> Self {
|
||||
Self::Magazine(class, count, ammo)
|
||||
}
|
||||
|
||||
/// The item is a magazine
|
||||
#[must_use]
|
||||
pub const fn is_magazine(&self) -> bool {
|
||||
matches!(self, Self::Magazine(_, _, _))
|
||||
}
|
||||
|
||||
/// The class name of the item
|
||||
#[must_use]
|
||||
pub fn class(&self) -> &str {
|
||||
match self {
|
||||
Self::Item(c, _) | Self::Magazine(c, _, _) => c.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the class name of the item
|
||||
pub fn set_class(&mut self, class: String) {
|
||||
match self {
|
||||
Self::Item(c, _) | Self::Magazine(c, _, _) => *c = class,
|
||||
}
|
||||
}
|
||||
|
||||
/// The amount of the item
|
||||
#[must_use]
|
||||
pub fn count(&self) -> u32 {
|
||||
match self {
|
||||
Self::Item(_, c) | Self::Magazine(_, c, _) => c.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the amount of the item
|
||||
pub fn set_count(&mut self, count: u32) {
|
||||
match self {
|
||||
Self::Item(_, c) | Self::Magazine(_, c, _) => *c = count,
|
||||
}
|
||||
}
|
||||
|
||||
/// The amount of ammo in the magazine
|
||||
#[must_use]
|
||||
pub fn ammo(&self) -> Option<u32> {
|
||||
match self {
|
||||
Self::Item(..) => None,
|
||||
Self::Magazine(_, _, a) => Some(a.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the amount of ammo in the magazine
|
||||
/// Returns true if the ammo was set, false if the item is not a magazine
|
||||
pub fn set_ammo(&mut self, ammo: u32) -> bool {
|
||||
match self {
|
||||
Self::Magazine(_, _, a) => {
|
||||
*a = ammo;
|
||||
true
|
||||
}
|
||||
Self::Item(..) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromArma for InventoryItem {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let commas = s.matches(',').count();
|
||||
match commas {
|
||||
1 => <(String, u32)>::from_arma(s).map(|(name, count)| Self::Item(name, count)),
|
||||
2 => <(String, u32, u32)>::from_arma(s)
|
||||
.map(|(name, count, ammo)| Self::Magazine(name, count, ammo)),
|
||||
_ => Err(FromArmaError::custom(format!(
|
||||
"Invalid inventory item: {s}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl IntoArma for InventoryItem {
|
||||
fn to_arma(&self) -> Value {
|
||||
match self {
|
||||
Self::Item(name, count) => Value::Array(vec![
|
||||
Value::String(name.clone()),
|
||||
Value::Number(f64::from(*count)),
|
||||
]),
|
||||
Self::Magazine(name, count, ammo) => Value::Array(vec![
|
||||
Value::String(name.clone()),
|
||||
Value::Number(f64::from(*count)),
|
||||
Value::Number(f64::from(*ammo)),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{FromArma, IntoArma, Value};
|
||||
|
||||
use super::InventoryItem;
|
||||
|
||||
#[test]
|
||||
fn is() {
|
||||
let item = InventoryItem::new_item("test".to_owned(), 1);
|
||||
assert_eq!(item.class(), "test");
|
||||
assert_eq!(item.count(), 1);
|
||||
assert_eq!(item.ammo(), None);
|
||||
assert!(!item.is_magazine());
|
||||
let item = InventoryItem::new_magazine("test".to_owned(), 1, 1);
|
||||
assert_eq!(item.class(), "test");
|
||||
assert_eq!(item.count(), 1);
|
||||
assert_eq!(item.ammo(), Some(1));
|
||||
assert!(item.is_magazine());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class() {
|
||||
let mut item = InventoryItem::new_item("test".to_owned(), 1);
|
||||
assert_eq!(item.class(), "test");
|
||||
item.set_class("test2".to_owned());
|
||||
assert_eq!(item.class(), "test2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count() {
|
||||
let mut item = InventoryItem::new_item("test".to_owned(), 1);
|
||||
assert_eq!(item.count(), 1);
|
||||
item.set_count(2);
|
||||
assert_eq!(item.count(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ammo() {
|
||||
let item = InventoryItem::new_magazine("test".to_owned(), 1, 1);
|
||||
assert_eq!(item.ammo(), Some(1));
|
||||
assert!(item.is_magazine());
|
||||
let item = InventoryItem::new_item("test".to_owned(), 1);
|
||||
assert_eq!(item.ammo(), None);
|
||||
assert!(!item.is_magazine());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_arma() {
|
||||
let item = InventoryItem::from_arma("[\"test\",1]".to_owned()).unwrap();
|
||||
assert_eq!(item.class(), "test");
|
||||
assert_eq!(item.count(), 1);
|
||||
assert_eq!(item.ammo(), None);
|
||||
assert!(!item.is_magazine());
|
||||
let item = InventoryItem::from_arma("[\"test\",1,1]".to_owned()).unwrap();
|
||||
assert_eq!(item.class(), "test");
|
||||
assert_eq!(item.count(), 1);
|
||||
assert_eq!(item.ammo(), Some(1));
|
||||
assert!(item.is_magazine());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_arma() {
|
||||
let item = InventoryItem::new_item("test".to_owned(), 1);
|
||||
assert_eq!(
|
||||
item.to_arma(),
|
||||
Value::Array(vec![Value::String("test".to_owned()), Value::Number(1.0),])
|
||||
);
|
||||
let item = InventoryItem::new_magazine("test".to_owned(), 1, 1);
|
||||
assert_eq!(
|
||||
item.to_arma(),
|
||||
Value::Array(vec![
|
||||
Value::String("test".to_owned()),
|
||||
Value::Number(1.0),
|
||||
Value::Number(1.0),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
135
vendor/arma-rs/src/value/loadout/magazine.rs
vendored
Normal file
135
vendor/arma-rs/src/value/loadout/magazine.rs
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
use crate::{FromArma, FromArmaError, IntoArma, Value};
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
/// A magazine loaded into a weapon
|
||||
pub struct Magazine(Option<(String, u32)>);
|
||||
impl Magazine {
|
||||
/// Create a new magazine
|
||||
#[must_use]
|
||||
pub const fn new(class: String, count: u32) -> Self {
|
||||
Self(Some((class, count)))
|
||||
}
|
||||
|
||||
/// The magazine exists
|
||||
#[must_use]
|
||||
pub const fn exists(&self) -> bool {
|
||||
self.0.is_some()
|
||||
}
|
||||
|
||||
/// Arma class name of the magazine
|
||||
#[must_use]
|
||||
pub fn class(&self) -> Option<&str> {
|
||||
self.0.as_ref().map(|(c, _)| c.as_str())
|
||||
}
|
||||
|
||||
/// Set the class name of the magazine
|
||||
pub fn set_class(&mut self, class: &str) {
|
||||
if let Some(magazine) = self.0.as_mut() {
|
||||
magazine.0 = class.to_string();
|
||||
} else {
|
||||
self.0 = Some((class.to_string(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
/// The remaining ammo in the magazine
|
||||
#[must_use]
|
||||
pub fn ammo(&self) -> Option<u32> {
|
||||
self.0.as_ref().map(|(_, a)| a.to_owned())
|
||||
}
|
||||
|
||||
/// Set the remaining ammo in the magazine
|
||||
/// Returns true if the ammo was set, false if the magazine was not initialized
|
||||
pub fn set_ammo(&mut self, ammo: u32) -> bool {
|
||||
if let Some(magazine) = self.0.as_mut() {
|
||||
magazine.1 = ammo;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromArma for Magazine {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
if s == "[]" {
|
||||
return Ok(Self(None));
|
||||
}
|
||||
<(String, u32)>::from_arma(s).map(|(name, count)| Self(Some((name, count))))
|
||||
}
|
||||
}
|
||||
impl IntoArma for Magazine {
|
||||
fn to_arma(&self) -> Value {
|
||||
self.0.as_ref().map_or_else(
|
||||
|| Value::Array(vec![]),
|
||||
|magazine| {
|
||||
Value::Array(vec![
|
||||
Value::String(magazine.0.clone()),
|
||||
Value::Number(f64::from(magazine.1)),
|
||||
])
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Magazine;
|
||||
use crate::{FromArma, IntoArma, Value};
|
||||
|
||||
#[test]
|
||||
fn test_magazine() {
|
||||
let mut magazine = Magazine::new("ACE_M84".to_string(), 10);
|
||||
assert!(magazine.exists());
|
||||
assert_eq!(magazine.class(), Some("ACE_M84"));
|
||||
assert_eq!(magazine.ammo(), Some(10));
|
||||
magazine.set_class("ACE_M84_HEDP");
|
||||
assert_eq!(magazine.class(), Some("ACE_M84_HEDP"));
|
||||
magazine.set_ammo(20);
|
||||
assert_eq!(magazine.ammo(), Some(20));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exists() {
|
||||
let magazine = Magazine::default();
|
||||
assert!(!magazine.exists());
|
||||
let magazine = Magazine::new("ACE_M84".to_string(), 10);
|
||||
assert!(magazine.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class() {
|
||||
let mut magazine = Magazine::new("ACE_M84".to_string(), 10);
|
||||
assert_eq!(magazine.class(), Some("ACE_M84"));
|
||||
magazine.set_class("ACE_M84_HEDP");
|
||||
assert_eq!(magazine.class(), Some("ACE_M84_HEDP"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ammo() {
|
||||
let mut magazine = Magazine::new("ACE_M84".to_string(), 10);
|
||||
assert_eq!(magazine.ammo(), Some(10));
|
||||
magazine.set_ammo(20);
|
||||
assert_eq!(magazine.ammo(), Some(20));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_arma() {
|
||||
let magazine = Magazine::from_arma("[]".to_string()).unwrap();
|
||||
assert!(!magazine.exists());
|
||||
let magazine = Magazine::from_arma("[\"ACE_M84\", 10]".to_string()).unwrap();
|
||||
assert!(magazine.exists());
|
||||
assert_eq!(magazine.class(), Some("ACE_M84"));
|
||||
assert_eq!(magazine.ammo(), Some(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_arma() {
|
||||
let magazine = Magazine::new("ACE_M84".to_string(), 10);
|
||||
assert_eq!(
|
||||
magazine.to_arma(),
|
||||
Value::Array(vec![
|
||||
Value::String("ACE_M84".to_string()),
|
||||
Value::Number(10.0),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
455
vendor/arma-rs/src/value/loadout/mod.rs
vendored
Normal file
455
vendor/arma-rs/src/value/loadout/mod.rs
vendored
Normal file
@@ -0,0 +1,455 @@
|
||||
//! For working with Arma's unit loadout array
|
||||
|
||||
use crate::{FromArma, FromArmaError, IntoArma, Value};
|
||||
|
||||
mod assigned;
|
||||
mod container;
|
||||
mod extended;
|
||||
mod inventory_item;
|
||||
mod magazine;
|
||||
mod weapon;
|
||||
|
||||
pub use assigned::AssignedItems;
|
||||
pub use container::Container;
|
||||
pub use extended::CBAExtended;
|
||||
pub use inventory_item::InventoryItem;
|
||||
pub use magazine::Magazine;
|
||||
pub use weapon::Weapon;
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
/// Arma Unit Loadout Array
|
||||
pub struct Loadout(
|
||||
Weapon,
|
||||
Weapon,
|
||||
Weapon,
|
||||
Container,
|
||||
Container,
|
||||
Container,
|
||||
String,
|
||||
String,
|
||||
Weapon,
|
||||
AssignedItems,
|
||||
CBAExtended,
|
||||
);
|
||||
|
||||
impl Loadout {
|
||||
/// Get the primary weapon
|
||||
#[must_use]
|
||||
pub const fn primary(&self) -> &Weapon {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Get the primary weapon mutably
|
||||
pub fn primary_mut(&mut self) -> &mut Weapon {
|
||||
&mut self.0
|
||||
}
|
||||
|
||||
/// Set the primary weapon
|
||||
pub fn set_primary(&mut self, primary: Weapon) {
|
||||
self.0 = primary;
|
||||
}
|
||||
|
||||
/// Get the secondary weapon (launcher)
|
||||
#[must_use]
|
||||
pub const fn secondary(&self) -> &Weapon {
|
||||
&self.1
|
||||
}
|
||||
|
||||
/// Get the secondary weapon (launcher) mutably
|
||||
pub fn secondary_mut(&mut self) -> &mut Weapon {
|
||||
&mut self.1
|
||||
}
|
||||
|
||||
/// Set the secondary weapon (launcher)
|
||||
pub fn set_secondary(&mut self, secondary: Weapon) {
|
||||
self.1 = secondary;
|
||||
}
|
||||
|
||||
/// Get the handgun weapon
|
||||
#[must_use]
|
||||
pub const fn handgun(&self) -> &Weapon {
|
||||
&self.2
|
||||
}
|
||||
|
||||
/// Get the handgun weapon mutably
|
||||
pub fn handgun_mut(&mut self) -> &mut Weapon {
|
||||
&mut self.2
|
||||
}
|
||||
|
||||
/// Set the handgun weapon
|
||||
pub fn set_handgun(&mut self, handgun: Weapon) {
|
||||
self.2 = handgun;
|
||||
}
|
||||
|
||||
/// Get the uniform
|
||||
#[must_use]
|
||||
pub const fn uniform(&self) -> &Container {
|
||||
&self.3
|
||||
}
|
||||
|
||||
/// Get the uniform mutably
|
||||
pub fn uniform_mut(&mut self) -> &mut Container {
|
||||
&mut self.3
|
||||
}
|
||||
|
||||
/// Set the uniform
|
||||
pub fn set_uniform(&mut self, uniform: Container) {
|
||||
self.3 = uniform;
|
||||
}
|
||||
|
||||
/// Get the vest
|
||||
#[must_use]
|
||||
pub const fn vest(&self) -> &Container {
|
||||
&self.4
|
||||
}
|
||||
|
||||
/// Get the vest mutably
|
||||
pub fn vest_mut(&mut self) -> &mut Container {
|
||||
&mut self.4
|
||||
}
|
||||
|
||||
/// Set the vest
|
||||
pub fn set_vest(&mut self, vest: Container) {
|
||||
self.4 = vest;
|
||||
}
|
||||
|
||||
/// Get the backpack
|
||||
#[must_use]
|
||||
pub const fn backpack(&self) -> &Container {
|
||||
&self.5
|
||||
}
|
||||
|
||||
/// Get the backpack mutably
|
||||
pub fn backpack_mut(&mut self) -> &mut Container {
|
||||
&mut self.5
|
||||
}
|
||||
|
||||
/// Set the backpack
|
||||
pub fn set_backpack(&mut self, backpack: Container) {
|
||||
self.5 = backpack;
|
||||
}
|
||||
|
||||
/// The class name of the current headgear
|
||||
#[must_use]
|
||||
pub fn headgear(&self) -> &str {
|
||||
&self.6
|
||||
}
|
||||
|
||||
/// Set the class name of the current headgear
|
||||
pub fn set_headgear(&mut self, headgear: String) {
|
||||
self.6 = headgear;
|
||||
}
|
||||
|
||||
/// The class name of the current goggles / facewear
|
||||
#[must_use]
|
||||
pub fn goggles(&self) -> &str {
|
||||
&self.7
|
||||
}
|
||||
|
||||
/// Set the class name of the current goggles / facewear
|
||||
pub fn set_goggles(&mut self, goggles: String) {
|
||||
self.7 = goggles;
|
||||
}
|
||||
|
||||
/// Get the binocular
|
||||
#[must_use]
|
||||
pub const fn binoculars(&self) -> &Weapon {
|
||||
&self.8
|
||||
}
|
||||
|
||||
/// Get the binocular mutably
|
||||
pub fn binoculars_mut(&mut self) -> &mut Weapon {
|
||||
&mut self.8
|
||||
}
|
||||
|
||||
/// Set the binocular
|
||||
pub fn set_binoculars(&mut self, binoculars: Weapon) {
|
||||
self.8 = binoculars;
|
||||
}
|
||||
|
||||
/// Get the assigned items
|
||||
#[must_use]
|
||||
pub const fn assigned_items(&self) -> &AssignedItems {
|
||||
&self.9
|
||||
}
|
||||
|
||||
/// Get the assigned items mutably
|
||||
pub fn assigned_items_mut(&mut self) -> &mut AssignedItems {
|
||||
&mut self.9
|
||||
}
|
||||
|
||||
/// Set the assigned items
|
||||
pub fn set_assigned_items(&mut self, assigned_items: AssignedItems) {
|
||||
self.9 = assigned_items;
|
||||
}
|
||||
|
||||
/// Get the CBA Extended Loadout Array
|
||||
pub fn cba_extended(&self) -> &CBAExtended {
|
||||
&self.10
|
||||
}
|
||||
|
||||
/// Get a map of all items in the loadout and their quantities
|
||||
pub fn classes(&self) -> std::collections::HashMap<String, u32> {
|
||||
let mut items = std::collections::HashMap::new();
|
||||
self.0.classes().iter().for_each(|c| {
|
||||
*items.entry(c.to_string()).or_insert(0) += 1;
|
||||
});
|
||||
self.1.classes().iter().for_each(|c| {
|
||||
*items.entry(c.to_string()).or_insert(0) += 1;
|
||||
});
|
||||
self.2.classes().iter().for_each(|c| {
|
||||
*items.entry(c.to_string()).or_insert(0) += 1;
|
||||
});
|
||||
self.3.classes().iter().for_each(|(c, q)| {
|
||||
*items.entry(c.clone()).or_insert(0) += q;
|
||||
});
|
||||
self.4.classes().iter().for_each(|(c, q)| {
|
||||
*items.entry(c.clone()).or_insert(0) += q;
|
||||
});
|
||||
self.5.classes().iter().for_each(|(c, q)| {
|
||||
*items.entry(c.clone()).or_insert(0) += q;
|
||||
});
|
||||
*items.entry(self.6.clone()).or_insert(0) += 1;
|
||||
*items.entry(self.7.clone()).or_insert(0) += 1;
|
||||
self.8.classes().iter().for_each(|c| {
|
||||
*items.entry(c.to_string()).or_insert(0) += 1;
|
||||
});
|
||||
self.9.classes().iter().for_each(|c| {
|
||||
*items.entry(c.to_string()).or_insert(0) += 1;
|
||||
});
|
||||
items.remove("");
|
||||
items
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArma for Loadout {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
let vanilla = <(
|
||||
Weapon,
|
||||
Weapon,
|
||||
Weapon,
|
||||
Container,
|
||||
Container,
|
||||
Container,
|
||||
String,
|
||||
String,
|
||||
Weapon,
|
||||
AssignedItems,
|
||||
)>::from_arma(s.clone())
|
||||
.map(
|
||||
|(
|
||||
primary,
|
||||
secondary,
|
||||
handgun,
|
||||
uniform,
|
||||
vest,
|
||||
backpack,
|
||||
headgear,
|
||||
goggles,
|
||||
binoculars,
|
||||
linked_items,
|
||||
)| {
|
||||
Self(
|
||||
primary,
|
||||
secondary,
|
||||
handgun,
|
||||
uniform,
|
||||
vest,
|
||||
backpack,
|
||||
headgear,
|
||||
goggles,
|
||||
binoculars,
|
||||
linked_items,
|
||||
CBAExtended::default(),
|
||||
)
|
||||
},
|
||||
);
|
||||
if vanilla.is_err() {
|
||||
return <(
|
||||
(
|
||||
Weapon,
|
||||
Weapon,
|
||||
Weapon,
|
||||
Container,
|
||||
Container,
|
||||
Container,
|
||||
String,
|
||||
String,
|
||||
Weapon,
|
||||
AssignedItems,
|
||||
),
|
||||
CBAExtended,
|
||||
)>::from_arma(s)
|
||||
.map(
|
||||
|(
|
||||
(
|
||||
primary,
|
||||
secondary,
|
||||
handgun,
|
||||
uniform,
|
||||
vest,
|
||||
backpack,
|
||||
headgear,
|
||||
goggles,
|
||||
binoculars,
|
||||
linked_items,
|
||||
),
|
||||
extended,
|
||||
)| {
|
||||
Self(
|
||||
primary,
|
||||
secondary,
|
||||
handgun,
|
||||
uniform,
|
||||
vest,
|
||||
backpack,
|
||||
headgear,
|
||||
goggles,
|
||||
binoculars,
|
||||
linked_items,
|
||||
extended,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
vanilla
|
||||
}
|
||||
}
|
||||
impl IntoArma for Loadout {
|
||||
fn to_arma(&self) -> Value {
|
||||
Value::Array(vec![
|
||||
self.primary().to_arma(),
|
||||
self.secondary().to_arma(),
|
||||
self.handgun().to_arma(),
|
||||
self.uniform().to_arma(),
|
||||
self.vest().to_arma(),
|
||||
self.backpack().to_arma(),
|
||||
Value::String(self.headgear().to_owned()),
|
||||
Value::String(self.goggles().to_owned()),
|
||||
self.binoculars().to_arma(),
|
||||
self.assigned_items().to_arma(),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn b_soldier_at_f() {
|
||||
let loadout = r#"[["arifle_MXC_Holo_pointer_F", "", "acc_pointer_IR", "optic_Holosight", ["30Rnd_65x39_caseless_mag", 30], [], ""],
|
||||
["launch_B_Titan_short_F", "", "", "", ["Titan_AT", 1], [], ""],
|
||||
["hgun_P07_F", "", "", "", ["16Rnd_9x21_Mag", 16], [], ""],
|
||||
["U_B_CombatUniform_mcam", [["FirstAidKit", 1], ["30Rnd_65x39_caseless_mag", 2, 30], ["Chemlight_green", 1, 1]]],
|
||||
["V_PlateCarrier1_rgr", [["30Rnd_65x39_caseless_mag", 3, 30], ["16Rnd_9x21_Mag", 2, 16], ["SmokeShell", 1 ,1], ["SmokeShellGreen", 1, 1], ["Chemlight_green", 1, 1]]],
|
||||
["B_AssaultPack_mcamo_AT",[["Titan_AT", 2, 1]]],
|
||||
"H_HelmetB_light_desert", "G_Bandanna_tan",[],
|
||||
["ItemMap", "", "ItemRadio", "ItemCompass", "ItemWatch", "NVGoggles"]]"#;
|
||||
Loadout::from_arma(loadout.to_string()).unwrap();
|
||||
let loadout = r#"[["arifle_SPAR_02_blk_F","","","optic_Holosight_blk_F",["30Rnd_556x45_Stanag",30],[],""],[],["hgun_ACPC2_F","","","",["9Rnd_45ACP_Mag",8],[],""],["tacs_Uniform_Polo_TP_LS_TP_TB_NoLogo",[]],["V_PlateCarrier1_rgr_noflag_F",[]],[],"H_Cap_headphones","G_Shades_Black",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]]"#;
|
||||
let mut loadout = Loadout::from_arma(loadout.to_string()).unwrap();
|
||||
loadout.set_secondary({
|
||||
let mut weapon = Weapon::new("launch_B_Titan_short_F".to_string());
|
||||
weapon.set_primary_magazine(Magazine::new("Titan_AT".to_string(), 1));
|
||||
weapon
|
||||
});
|
||||
Loadout::from_arma(loadout.to_arma().to_string()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn marshal() {
|
||||
let loadout = r#"[[],[],[],["U_Marshal",[]],[],[],"H_Cap_headphones","G_Aviator",[],["ItemMap","ItemGPS","","ItemCompass","ItemWatch",""]]"#;
|
||||
let mut loadout = Loadout::from_arma(loadout.to_string()).unwrap();
|
||||
loadout.set_secondary({
|
||||
let mut weapon = Weapon::new("launch_B_Titan_short_F".to_string());
|
||||
weapon.set_primary_magazine(Magazine::new("Titan_AT".to_string(), 1));
|
||||
weapon
|
||||
});
|
||||
loadout.set_primary({
|
||||
let mut weapon = Weapon::new("arifle_MXC_F".to_string());
|
||||
weapon.set_optic("optic_Holosight".to_string());
|
||||
weapon
|
||||
});
|
||||
let uniform = loadout.uniform_mut();
|
||||
uniform.set_class("U_B_CombatUniform_mcam".to_string());
|
||||
let uniform_items = uniform.items_mut().unwrap();
|
||||
uniform_items.push(InventoryItem::new_item("FirstAidKit".to_string(), 3));
|
||||
uniform_items.push(InventoryItem::new_magazine(
|
||||
"30Rnd_65x39_caseless_mag".to_string(),
|
||||
5,
|
||||
30,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extended_empty() {
|
||||
let loadout = r#"[[["arifle_XMS_Shot_lxWS","","tacgt_ANPEQ_15_Low_Light_Black","CUP_optic_Elcan_SpecterDR_black_PIP",["tacgt_30Rnd_556x45_Ball_Tracer_PMAG",30],["6Rnd_12Gauge_Pellets",6],""],["CUP_launch_M136_Loaded","","","",[],[],""],[],["tacs_Uniform_Floral_JP_RS_LP_BB",[["kat_guedel",1],["ACE_EntrenchingTool",1],["ACE_EarPlugs",1],["ACE_CableTie",2],["ACE_quikclot",1],["ACE_packingBandage",2],["ACE_elasticBandage",1],["SmokeShell",2,1],["Chemlight_yellow",1,1],["Chemlight_red",3,1],["ACE_Chemlight_IR",2,1],["ACE_Chemlight_HiBlue",1,1]]],["milgp_v_mmac_marksman_belt_CB",[["ACE_tourniquet",4],["ACE_splint",1],["tacgt_30Rnd_556x45_EPR_PMAG",15,30],["tacgt_30Rnd_556x45_AP_PMAG",2,30],["6rnd_Smoke_Mag_lxWS",1,6]]],["milgp_bp_Pointman_cb",[["ACE_SpraypaintGreen",1],["ACE_splint",1],["synixe_painkillers",2],["ACE_microDAGR",1],["ACE_MapTools",1],["ACE_bodyBag",1],["ACE_packingBandage",10],["ACE_elasticBandage",9],["ACE_quikclot",5],["ACE_EarPlugs",2],["ACRE_PRC152",1],["ACRE_PRC152",1],["6Rnd_12Gauge_Pellets",2,6],["6Rnd_12Gauge_Slug",1,6],["tacgt_30Rnd_556x45_Ball_Tracer_PMAG",1,30]]],"synixe_contractors_Hat_Beret_Black","",["ACE_VectorDay","","","",[],[],""],["ItemMap","ItemGPS","","ItemCompass","ItemWatch",""]],[]]"#;
|
||||
let loadout = Loadout::from_arma(loadout.to_string()).unwrap();
|
||||
assert!(loadout.cba_extended().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extended_crash_1_9_0() {
|
||||
let loadout = r#"[[[],[],[],["synixe_contractors_Uniform_Contractor_Shirt",[]],[],[],"","",[],["","","","","",""]],[]]"#;
|
||||
let loadout = Loadout::from_arma(loadout.to_string()).unwrap();
|
||||
assert!(loadout.cba_extended().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extended_items() {
|
||||
let loadout = r#"[[["CUP_arifle_M4A1_SOMMOD_Grip_tan","","","CUP_optic_Eotech553_Black",["tacgt_30Rnd_556x45_EPR_PMAG_Tan",30],[],""],[],["ACE_VMM3","","","",[],[],""],["casual_plaid_gray_khaki_uniform",[["ACE_packingBandage",10],["ACE_elasticBandage",10],["ACE_CableTie",2],["kat_guedel",1],["ACE_tourniquet",2],["ACE_splint",1],["synixe_painkillers",2]]],["milgp_v_mmac_assaulter_belt_AOR2",[["ACRE_PRC152",1],["SmokeShell",2,1],["HandGrenade",2,1],["tacgt_30Rnd_556x45_EPR_PMAG_Tan",10,30]]],["B_MU_TacticalPack_cbr",[["ACE_bodyBag",1],["ToolKit",1],["ACE_SpraypaintGreen",1],["synixe_axe",1],["ACE_wirecutter",1],["ACE_EntrenchingTool",1],["ACE_rope3",2],["DemoCharge_Remote_Mag",2,1]]],"synixe_contractors_Cap_Headphones_GreenLogo","CUP_G_Tan_Scarf_Shades",["Binocular","","","",[],[],""],["ItemMap","ItemGPS","","ItemCompass","ItemWatch",""]],[["grad_slingHelmet","CUP_H_OpsCore_Grey"]]]"#;
|
||||
let loadout = Loadout::from_arma(loadout.to_string()).unwrap();
|
||||
assert!(!loadout.cba_extended().is_empty());
|
||||
assert_eq!(
|
||||
loadout.primary().class(),
|
||||
Some("CUP_arifle_M4A1_SOMMOD_Grip_tan")
|
||||
);
|
||||
assert_eq!(
|
||||
loadout.cba_extended().get("grad_slingHelmet"),
|
||||
Some(&Value::String("CUP_H_OpsCore_Grey".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classes() {
|
||||
let loadout = r#"[[["CUP_arifle_M4A1_SOMMOD_Grip_tan","","","CUP_optic_Eotech553_Black",["tacgt_30Rnd_556x45_EPR_PMAG_Tan",30],[],""],[],["ACE_VMM3","","","",[],[],""],["casual_plaid_gray_khaki_uniform",[["ACE_packingBandage",10],["ACE_elasticBandage",10],["ACE_CableTie",2],["kat_guedel",1],["ACE_tourniquet",2],["ACE_splint",1],["synixe_painkillers",2]]],["milgp_v_mmac_assaulter_belt_AOR2",[["ACRE_PRC152",1],["SmokeShell",2,1],["HandGrenade",2,1],["tacgt_30Rnd_556x45_EPR_PMAG_Tan",10,30]]],["B_MU_TacticalPack_cbr",[["ACE_bodyBag",1],["ToolKit",1],["ACE_SpraypaintGreen",1],["synixe_axe",1],["ACE_wirecutter",1],["ACE_EntrenchingTool",1],["ACE_rope3",2],["DemoCharge_Remote_Mag",2,1]]],"synixe_contractors_Cap_Headphones_GreenLogo","CUP_G_Tan_Scarf_Shades",["Binocular","","","",[],[],""],["ItemMap","ItemGPS","","ItemCompass","ItemWatch",""]],[["grad_slingHelmet","CUP_H_OpsCore_Grey"]]]"#;
|
||||
let loadout = Loadout::from_arma(loadout.to_string()).unwrap();
|
||||
assert_eq!(loadout.classes(), {
|
||||
let mut classes = std::collections::HashMap::new();
|
||||
classes.insert("CUP_arifle_M4A1_SOMMOD_Grip_tan".to_string(), 1);
|
||||
classes.insert("CUP_optic_Eotech553_Black".to_string(), 1);
|
||||
classes.insert("tacgt_30Rnd_556x45_EPR_PMAG_Tan".to_string(), 11);
|
||||
classes.insert("ACE_VMM3".to_string(), 1);
|
||||
classes.insert("casual_plaid_gray_khaki_uniform".to_string(), 1);
|
||||
classes.insert("ACE_packingBandage".to_string(), 10);
|
||||
classes.insert("ACE_elasticBandage".to_string(), 10);
|
||||
classes.insert("ACE_CableTie".to_string(), 2);
|
||||
classes.insert("kat_guedel".to_string(), 1);
|
||||
classes.insert("ACE_tourniquet".to_string(), 2);
|
||||
classes.insert("ACE_splint".to_string(), 1);
|
||||
classes.insert("synixe_painkillers".to_string(), 2);
|
||||
classes.insert("milgp_v_mmac_assaulter_belt_AOR2".to_string(), 1);
|
||||
classes.insert("ACRE_PRC152".to_string(), 1);
|
||||
classes.insert("SmokeShell".to_string(), 2);
|
||||
classes.insert("HandGrenade".to_string(), 2);
|
||||
classes.insert("B_MU_TacticalPack_cbr".to_string(), 1);
|
||||
classes.insert("ACE_bodyBag".to_string(), 1);
|
||||
classes.insert("ToolKit".to_string(), 1);
|
||||
classes.insert("ACE_SpraypaintGreen".to_string(), 1);
|
||||
classes.insert("synixe_axe".to_string(), 1);
|
||||
classes.insert("ACE_wirecutter".to_string(), 1);
|
||||
classes.insert("ACE_EntrenchingTool".to_string(), 1);
|
||||
classes.insert("ACE_rope3".to_string(), 2);
|
||||
classes.insert("DemoCharge_Remote_Mag".to_string(), 2);
|
||||
classes.insert("synixe_contractors_Cap_Headphones_GreenLogo".to_string(), 1);
|
||||
classes.insert("CUP_G_Tan_Scarf_Shades".to_string(), 1);
|
||||
classes.insert("Binocular".to_string(), 1);
|
||||
classes.insert("ItemMap".to_string(), 1);
|
||||
classes.insert("ItemGPS".to_string(), 1);
|
||||
classes.insert("ItemCompass".to_string(), 1);
|
||||
classes.insert("ItemWatch".to_string(), 1);
|
||||
classes
|
||||
});
|
||||
}
|
||||
}
|
||||
387
vendor/arma-rs/src/value/loadout/weapon.rs
vendored
Normal file
387
vendor/arma-rs/src/value/loadout/weapon.rs
vendored
Normal file
@@ -0,0 +1,387 @@
|
||||
use crate::{FromArma, FromArmaError, IntoArma, Value};
|
||||
|
||||
use super::Magazine;
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
/// A primary, secondary, or handgun weapon
|
||||
pub struct Weapon(Option<(String, String, String, String, Magazine, Magazine, String)>);
|
||||
impl Weapon {
|
||||
/// Create a new weapon
|
||||
#[must_use]
|
||||
pub fn new(class: String) -> Self {
|
||||
Self(Some((
|
||||
class,
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
Magazine::default(),
|
||||
Magazine::default(),
|
||||
"".to_string(),
|
||||
)))
|
||||
}
|
||||
|
||||
/// The weapon slot is occupied
|
||||
#[must_use]
|
||||
pub const fn exists(&self) -> bool {
|
||||
self.0.is_some()
|
||||
}
|
||||
|
||||
/// The class name of the weapon
|
||||
#[must_use]
|
||||
pub fn class(&self) -> Option<&str> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|(class, _, _, _, _, _, _)| class.as_str())
|
||||
}
|
||||
|
||||
/// Set the class name of the weapon
|
||||
pub fn set_class(&mut self, class: String) {
|
||||
if let Some(weapon) = self.0.as_mut() {
|
||||
weapon.0 = class;
|
||||
} else {
|
||||
self.0 = Some((
|
||||
class,
|
||||
"".to_owned(),
|
||||
"".to_owned(),
|
||||
"".to_owned(),
|
||||
Magazine::default(),
|
||||
Magazine::default(),
|
||||
"".to_owned(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// The class name of the attached suppressor
|
||||
#[must_use]
|
||||
pub fn suppressor(&self) -> Option<&str> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|(_, suppressor, _, _, _, _, _)| suppressor.as_str())
|
||||
}
|
||||
|
||||
/// Set the class name of the attached suppressor
|
||||
/// Returns true if the suppressor was set, false if the weapon was not initialized
|
||||
pub fn set_suppressor(&mut self, suppressor: String) -> bool {
|
||||
if let Some(weapon) = self.0.as_mut() {
|
||||
weapon.1 = suppressor;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// The class name of the attached pointer
|
||||
#[must_use]
|
||||
pub fn pointer(&self) -> Option<&str> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|(_, _, pointer, _, _, _, _)| pointer.as_str())
|
||||
}
|
||||
|
||||
/// Set the class name of the attached pointer
|
||||
/// Returns true if the pointer was set, false if the weapon was not initialized
|
||||
pub fn set_pointer(&mut self, pointer: String) -> bool {
|
||||
if let Some(weapon) = self.0.as_mut() {
|
||||
weapon.2 = pointer;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// The class name of the attached optic
|
||||
#[must_use]
|
||||
pub fn optic(&self) -> Option<&str> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|(_, _, _, optic, _, _, _)| optic.as_str())
|
||||
}
|
||||
|
||||
/// Set the class name of the attached optic
|
||||
/// Returns true if the optic was set, false if the weapon was not initialized
|
||||
pub fn set_optic(&mut self, optic: String) -> bool {
|
||||
if let Some(weapon) = self.0.as_mut() {
|
||||
weapon.3 = optic;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the inserted primary magazine
|
||||
#[must_use]
|
||||
pub fn primary_magazine(&self) -> Option<&Magazine> {
|
||||
self.0.as_ref().map(|(_, _, _, _, primary, _, _)| primary)
|
||||
}
|
||||
|
||||
/// Get the inserted primary magazine mutably
|
||||
pub fn primary_magazine_mut(&mut self) -> Option<&mut Magazine> {
|
||||
self.0.as_mut().map(|(_, _, _, _, primary, _, _)| primary)
|
||||
}
|
||||
|
||||
/// Set the inserted primary magazine
|
||||
/// Returns true if the primary magazine was set, false if the weapon was not initialized
|
||||
pub fn set_primary_magazine(&mut self, primary: Magazine) -> bool {
|
||||
if let Some(weapon) = self.0.as_mut() {
|
||||
weapon.4 = primary;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the inserted secondary magazine
|
||||
#[must_use]
|
||||
pub fn secondary_magazine(&self) -> Option<&Magazine> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|(_, _, _, _, _, secondary, _)| secondary)
|
||||
}
|
||||
|
||||
/// Get the inserted secondary magazine mutably
|
||||
pub fn secondary_magazine_mut(&mut self) -> Option<&mut Magazine> {
|
||||
self.0
|
||||
.as_mut()
|
||||
.map(|(_, _, _, _, _, secondary, _)| secondary)
|
||||
}
|
||||
|
||||
/// Set the inserted secondary magazine
|
||||
/// Returns true if the secondary magazine was set, false if the weapon was not initialized
|
||||
pub fn set_secondary_magazine(&mut self, secondary: Magazine) -> bool {
|
||||
if let Some(weapon) = self.0.as_mut() {
|
||||
weapon.5 = secondary;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// The class name of the attached bipod
|
||||
#[must_use]
|
||||
pub fn bipod(&self) -> Option<&str> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|(_, _, _, _, _, _, bipod)| bipod.as_str())
|
||||
}
|
||||
|
||||
/// Set the class name of the attached bipod
|
||||
/// Returns true if the bipod was set, false if the weapon was not initialized
|
||||
pub fn set_bipod(&mut self, bipod: String) -> bool {
|
||||
if let Some(weapon) = self.0.as_mut() {
|
||||
weapon.6 = bipod;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all classes used on the weapon, including the weapon itself
|
||||
pub fn classes(&self) -> Vec<&str> {
|
||||
let mut classes = Vec::new();
|
||||
if let Some(weapon) = self.0.as_ref() {
|
||||
classes.push(weapon.0.as_str());
|
||||
if !weapon.1.is_empty() {
|
||||
classes.push(weapon.1.as_str());
|
||||
}
|
||||
if !weapon.2.is_empty() {
|
||||
classes.push(weapon.2.as_str());
|
||||
}
|
||||
if !weapon.3.is_empty() {
|
||||
classes.push(weapon.3.as_str());
|
||||
}
|
||||
if let Some(class) = weapon.4.class() {
|
||||
classes.push(class);
|
||||
}
|
||||
if let Some(class) = weapon.5.class() {
|
||||
classes.push(class);
|
||||
}
|
||||
if !weapon.6.is_empty() {
|
||||
classes.push(weapon.6.as_str());
|
||||
}
|
||||
}
|
||||
classes
|
||||
}
|
||||
}
|
||||
impl FromArma for Weapon {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
if s == "[]" {
|
||||
return Ok(Self(None));
|
||||
}
|
||||
<(String, String, String, String, Magazine, Magazine, String)>::from_arma(s).map(
|
||||
|(weapon, suppressor, pointer, optic, primary_mag, secondary_mag, bipod)| {
|
||||
Self(Some((
|
||||
weapon,
|
||||
suppressor,
|
||||
pointer,
|
||||
optic,
|
||||
primary_mag,
|
||||
secondary_mag,
|
||||
bipod,
|
||||
)))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
impl IntoArma for Weapon {
|
||||
fn to_arma(&self) -> Value {
|
||||
self.0.as_ref().map_or_else(
|
||||
|| Value::Array(vec![]),
|
||||
|weaon| {
|
||||
Value::Array(vec![
|
||||
Value::String(weaon.0.clone()),
|
||||
Value::String(weaon.1.clone()),
|
||||
Value::String(weaon.2.clone()),
|
||||
Value::String(weaon.3.clone()),
|
||||
weaon.4.to_arma(),
|
||||
weaon.5.to_arma(),
|
||||
Value::String(weaon.6.clone()),
|
||||
])
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Weapon;
|
||||
use crate::{loadout::Magazine, FromArma, IntoArma, Value};
|
||||
|
||||
#[test]
|
||||
fn exists() {
|
||||
let weapon = Weapon::default();
|
||||
assert!(!weapon.exists());
|
||||
let weapon = Weapon::new("arifle_Mk20_GL_F".to_owned());
|
||||
assert!(weapon.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class() {
|
||||
let weapon = Weapon::default();
|
||||
assert_eq!(weapon.class(), None);
|
||||
let mut weapon = Weapon::new("arifle_Mk20_GL_F".to_owned());
|
||||
assert_eq!(weapon.class(), Some("arifle_Mk20_GL_F"));
|
||||
weapon.set_class("arifle_Mk20_F".to_owned());
|
||||
assert_eq!(weapon.class(), Some("arifle_Mk20_F"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suppressor() {
|
||||
let weapon = Weapon::default();
|
||||
assert_eq!(weapon.suppressor(), None);
|
||||
let mut weapon = Weapon::new("arifle_Mk20_GL_F".to_owned());
|
||||
assert_eq!(weapon.suppressor(), Some(""));
|
||||
weapon.set_suppressor("muzzle_snds_H".to_owned());
|
||||
assert_eq!(weapon.suppressor(), Some("muzzle_snds_H"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pointer() {
|
||||
let weapon = Weapon::default();
|
||||
assert_eq!(weapon.pointer(), None);
|
||||
let mut weapon = Weapon::new("arifle_Mk20_GL_F".to_owned());
|
||||
assert_eq!(weapon.pointer(), Some(""));
|
||||
weapon.set_pointer("acc_pointer_IR".to_owned());
|
||||
assert_eq!(weapon.pointer(), Some("acc_pointer_IR"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optic() {
|
||||
let weapon = Weapon::default();
|
||||
assert_eq!(weapon.optic(), None);
|
||||
let mut weapon = Weapon::new("arifle_Mk20_GL_F".to_owned());
|
||||
assert_eq!(weapon.optic(), Some(""));
|
||||
weapon.set_optic("optic_Hamr".to_owned());
|
||||
assert_eq!(weapon.optic(), Some("optic_Hamr"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn primary_magazine() {
|
||||
let weapon = Weapon::default();
|
||||
assert_eq!(weapon.primary_magazine(), None);
|
||||
let mut weapon = Weapon::new("arifle_Mk20_GL_F".to_owned());
|
||||
assert_eq!(weapon.primary_magazine(), Some(&Magazine::default()));
|
||||
weapon.set_primary_magazine(Magazine::new("30Rnd_556x45_Stanag".to_string(), 30));
|
||||
assert_eq!(
|
||||
weapon.primary_magazine(),
|
||||
Some(&Magazine::new("30Rnd_556x45_Stanag".to_string(), 30))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secondary_magazine() {
|
||||
let weapon = Weapon::default();
|
||||
assert_eq!(weapon.secondary_magazine(), None);
|
||||
let mut weapon = Weapon::new("arifle_Mk20_GL_F".to_owned());
|
||||
assert_eq!(weapon.secondary_magazine(), Some(&Magazine::default()));
|
||||
weapon.set_secondary_magazine(Magazine::new("1Rnd_HE_Grenade_shell".to_string(), 1));
|
||||
assert_eq!(
|
||||
weapon.secondary_magazine(),
|
||||
Some(&Magazine::new("1Rnd_HE_Grenade_shell".to_string(), 1))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bipod() {
|
||||
let weapon = Weapon::default();
|
||||
assert_eq!(weapon.bipod(), None);
|
||||
let mut weapon = Weapon::new("arifle_Mk20_GL_F".to_owned());
|
||||
assert_eq!(weapon.bipod(), Some(""));
|
||||
weapon.set_bipod("bipod_01_F".to_owned());
|
||||
assert_eq!(weapon.bipod(), Some("bipod_01_F"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_arma() {
|
||||
let weapon = Weapon::from_arma("[]".to_owned()).unwrap();
|
||||
assert_eq!(weapon, Weapon::default());
|
||||
let weapon = Weapon::from_arma(
|
||||
"[\"arifle_Mk20_GL_F\",\"\",\"\",\"\",[\"30Rnd_556x45_Stanag\",30],[\"1Rnd_HE_Grenade_shell\",1],\"\"]".to_owned(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(weapon, {
|
||||
let mut weapon = Weapon::new("arifle_Mk20_GL_F".to_owned());
|
||||
weapon.set_primary_magazine(Magazine::new("30Rnd_556x45_Stanag".to_string(), 30));
|
||||
weapon.set_secondary_magazine(Magazine::new("1Rnd_HE_Grenade_shell".to_string(), 1));
|
||||
weapon
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_arma() {
|
||||
let weapon = Weapon::default();
|
||||
assert_eq!(weapon.to_arma(), Value::Array(vec![]));
|
||||
let weapon = Weapon::new("arifle_Mk20_GL_F".to_owned());
|
||||
assert_eq!(
|
||||
weapon.to_arma(),
|
||||
Value::Array(vec![
|
||||
Value::String("arifle_Mk20_GL_F".to_owned()),
|
||||
Value::String("".to_owned()),
|
||||
Value::String("".to_owned()),
|
||||
Value::String("".to_owned()),
|
||||
Value::Array(vec![]),
|
||||
Value::Array(vec![]),
|
||||
Value::String("".to_owned()),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classes() {
|
||||
let weapon = Weapon::from_arma("[]".to_owned()).unwrap();
|
||||
assert_eq!(weapon, Weapon::default());
|
||||
let weapon = Weapon::from_arma(
|
||||
"[\"arifle_Mk20_GL_F\",\"fake_optic\",\"\",\"\",[\"30Rnd_556x45_Stanag\",30],[\"1Rnd_HE_Grenade_shell\",1],\"fake_bipod\"]".to_owned(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
weapon.classes(),
|
||||
vec![
|
||||
"arifle_Mk20_GL_F",
|
||||
"fake_optic",
|
||||
"30Rnd_556x45_Stanag",
|
||||
"1Rnd_HE_Grenade_shell",
|
||||
"fake_bipod"
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
145
vendor/arma-rs/src/value/mod.rs
vendored
Normal file
145
vendor/arma-rs/src/value/mod.rs
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
mod features;
|
||||
mod from_arma;
|
||||
mod into_arma;
|
||||
pub mod loadout;
|
||||
|
||||
pub use from_arma::{FromArma, FromArmaError};
|
||||
pub use into_arma::{DirectReturn, IntoArma};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||
/// A value that can be converted to and from Arma types.
|
||||
pub enum Value {
|
||||
/// Arma's `nil` value.
|
||||
/// Represented as `null`
|
||||
Null,
|
||||
/// Arma's `number` value.
|
||||
Number(f64),
|
||||
/// Arma's `array` value.
|
||||
/// Represented as `[...]`
|
||||
Array(Vec<Value>),
|
||||
/// Arma's `boolean` value.
|
||||
/// Represented as `true` or `false`
|
||||
Boolean(bool),
|
||||
/// Arma's `string` value.
|
||||
/// Represented as `"..."`
|
||||
///
|
||||
/// Note: Arma escapes quotes with two double quotes.
|
||||
/// This conversation will remove one step of escaping.
|
||||
/// Example: `"My name is ""John""."` will become `My name is "John".`
|
||||
String(String),
|
||||
/// Unknown value. Contains the raw string.
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Null => write!(f, "null"),
|
||||
Self::Number(n) => write!(f, "{n}"),
|
||||
Self::Array(a) => write!(
|
||||
f,
|
||||
"[{}]",
|
||||
a.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
),
|
||||
Self::Boolean(b) => write!(f, "{b}"),
|
||||
Self::String(s) => write!(f, "\"{}\"", s.replace('\"', "\"\"")),
|
||||
Self::Unknown(s) => write!(f, "Unknown({})", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArma for Value {
|
||||
fn from_arma(s: String) -> Result<Self, FromArmaError> {
|
||||
match s.chars().next() {
|
||||
Some('n') => Ok(Self::Null),
|
||||
Some('t') | Some('f') => Ok(Value::Boolean(<bool>::from_arma(s)?)),
|
||||
Some('0'..='9') | Some('-') => Ok(Value::Number(<f64>::from_arma(s)?)),
|
||||
Some('[') => Ok(Value::Array(<Vec<Value>>::from_arma(s)?)),
|
||||
Some('"') => Ok(Value::String(<String>::from_arma(s)?)),
|
||||
_ => Ok(Value::Unknown(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_value_display() {
|
||||
assert_eq!(Value::Null.to_string(), "null");
|
||||
assert_eq!(Value::Number(1.0).to_string(), "1");
|
||||
assert_eq!(Value::Number(1.5).to_string(), "1.5");
|
||||
assert_eq!(Value::Number(-1.5).to_string(), "-1.5");
|
||||
assert_eq!(Value::Boolean(true).to_string(), "true");
|
||||
assert_eq!(Value::Boolean(false).to_string(), "false");
|
||||
assert_eq!(Value::String("".to_string()).to_string(), "\"\"");
|
||||
assert_eq!(Value::String(" ".to_string()).to_string(), "\" \"");
|
||||
assert_eq!(Value::String("Hello".to_string()).to_string(), "\"Hello\"");
|
||||
assert_eq!(
|
||||
Value::String("Hello \"World\"".to_string()).to_string(),
|
||||
"\"Hello \"\"World\"\"\""
|
||||
);
|
||||
assert_eq!(
|
||||
Value::Array(vec![
|
||||
Value::Number(1.0),
|
||||
Value::Number(2.0),
|
||||
Value::Number(3.0)
|
||||
])
|
||||
.to_string(),
|
||||
"[1,2,3]"
|
||||
);
|
||||
assert_eq!(
|
||||
Value::Array(vec![
|
||||
Value::String("Hello".to_string()),
|
||||
Value::String("World".to_string())
|
||||
])
|
||||
.to_string(),
|
||||
"[\"Hello\",\"World\"]"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_from_arma() {
|
||||
let value = Value::from_arma("null".to_string()).unwrap();
|
||||
assert_eq!(value, Value::Null);
|
||||
let value = Value::from_arma("true".to_string()).unwrap();
|
||||
assert_eq!(value, Value::Boolean(true));
|
||||
let value = Value::from_arma("false".to_string()).unwrap();
|
||||
assert_eq!(value, Value::Boolean(false));
|
||||
let value = Value::from_arma("1".to_string()).unwrap();
|
||||
assert_eq!(value, Value::Number(1.0));
|
||||
let value = Value::from_arma("1.5".to_string()).unwrap();
|
||||
assert_eq!(value, Value::Number(1.5));
|
||||
let value = Value::from_arma("-1.5".to_string()).unwrap();
|
||||
assert_eq!(value, Value::Number(-1.5));
|
||||
let value = Value::from_arma("[1,2,3]".to_string()).unwrap();
|
||||
assert_eq!(
|
||||
value,
|
||||
Value::Array(vec![
|
||||
Value::Number(1.0),
|
||||
Value::Number(2.0),
|
||||
Value::Number(3.0)
|
||||
])
|
||||
);
|
||||
let value = Value::from_arma("[\"Hello\",\"World\"]".to_string()).unwrap();
|
||||
assert_eq!(
|
||||
value,
|
||||
Value::Array(vec![
|
||||
Value::String("Hello".to_string()),
|
||||
Value::String("World".to_string())
|
||||
])
|
||||
);
|
||||
let value = Value::from_arma("\"Hello\"".to_string()).unwrap();
|
||||
assert_eq!(value, Value::String("Hello".to_string()));
|
||||
let value = Value::from_arma("\"Hello \"\"World\"\"\"".to_string()).unwrap();
|
||||
assert_eq!(value, Value::String("Hello \"World\"".to_string()));
|
||||
}
|
||||
}
|
||||
613
vendor/arma-rs/tests/derive.rs
vendored
Normal file
613
vendor/arma-rs/tests/derive.rs
vendored
Normal file
@@ -0,0 +1,613 @@
|
||||
mod derive {
|
||||
use arma_rs::{FromArma, FromArmaError, IntoArma, Value};
|
||||
|
||||
fn sort_value_array(value: &mut Value) -> &Value {
|
||||
if let Value::Array(values) = value {
|
||||
values.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
enum ValueStringImpl {
|
||||
#[default]
|
||||
Even,
|
||||
Odd,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ValueStringImpl {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Even => write!(f, "even"),
|
||||
Self::Odd => write!(f, "odd"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for ValueStringImpl {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.chars().count() % 2 {
|
||||
0 => Ok(Self::Even),
|
||||
_ => Ok(Self::Odd),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(miri))]
|
||||
fn compile() {
|
||||
let tests = trybuild::TestCases::new();
|
||||
tests.compile_fail("tests/derive/*fail*.rs");
|
||||
tests.pass("tests/derive/*pass*.rs");
|
||||
}
|
||||
|
||||
mod map {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn derive() {
|
||||
#[derive(FromArma, IntoArma, Debug, PartialEq)]
|
||||
struct DeriveTest {
|
||||
first: String,
|
||||
second: bool,
|
||||
}
|
||||
|
||||
let serialized = DeriveTest {
|
||||
first: "first".to_string(),
|
||||
second: true,
|
||||
};
|
||||
let deserialized = Value::Array(vec![
|
||||
Value::Array(vec![
|
||||
Value::String("first".to_string()),
|
||||
Value::String("first".to_string()),
|
||||
]),
|
||||
Value::Array(vec![
|
||||
Value::String("second".to_string()),
|
||||
Value::Boolean(true),
|
||||
]),
|
||||
]);
|
||||
|
||||
assert_eq!(sort_value_array(&mut serialized.to_arma()), &deserialized);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(deserialized.to_string()),
|
||||
Ok(serialized)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transparent() {
|
||||
#[derive(FromArma, IntoArma, Debug, PartialEq)]
|
||||
#[arma(transparent)]
|
||||
struct DeriveTest {
|
||||
expected: String,
|
||||
}
|
||||
|
||||
let serialized = DeriveTest {
|
||||
expected: "expected".to_string(),
|
||||
};
|
||||
let deserialized = Value::String("expected".to_string());
|
||||
assert_eq!(serialized.to_arma(), deserialized);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(deserialized.to_string()),
|
||||
Ok(serialized)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str_field() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
struct DeriveTest {
|
||||
#[arma(from_str)]
|
||||
expected: ValueStringImpl,
|
||||
}
|
||||
|
||||
let input = Value::Array(vec![Value::Array(vec![
|
||||
Value::String("expected".to_string()),
|
||||
Value::String("odd".to_string()),
|
||||
])]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest {
|
||||
expected: ValueStringImpl::Odd,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string_field() {
|
||||
#[derive(IntoArma, Debug, PartialEq)]
|
||||
struct DeriveTest {
|
||||
#[arma(to_string)]
|
||||
expected: ValueStringImpl,
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
DeriveTest {
|
||||
expected: ValueStringImpl::Odd,
|
||||
}
|
||||
.to_arma(),
|
||||
Value::Array(vec![Value::Array(vec![
|
||||
Value::String("expected".to_string()),
|
||||
Value::String("odd".to_string()),
|
||||
])])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str_default_field() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
struct DeriveTest {
|
||||
#[arma(from_str, default)]
|
||||
expected: ValueStringImpl,
|
||||
}
|
||||
|
||||
let input = Value::Array(vec![]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest {
|
||||
expected: ValueStringImpl::default(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
#[derive(FromArma, Default, Debug, PartialEq)]
|
||||
#[arma(default)]
|
||||
struct DeriveTest {
|
||||
first: String,
|
||||
second: bool,
|
||||
}
|
||||
|
||||
let input = Value::Array(vec![]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest::default())
|
||||
);
|
||||
|
||||
let input = Value::Array(vec![Value::Array(vec![
|
||||
Value::String("first".to_string()),
|
||||
Value::String("first".to_string()),
|
||||
])]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest {
|
||||
first: "first".to_string(),
|
||||
..DeriveTest::default()
|
||||
})
|
||||
);
|
||||
|
||||
let input = Value::Array(vec![Value::Array(vec![
|
||||
Value::String("second".to_string()),
|
||||
Value::Boolean(true),
|
||||
])]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest {
|
||||
second: true,
|
||||
..DeriveTest::default()
|
||||
})
|
||||
);
|
||||
|
||||
let input = Value::Array(vec![
|
||||
Value::Array(vec![
|
||||
Value::String("first".to_string()),
|
||||
Value::String("first".to_string()),
|
||||
]),
|
||||
Value::Array(vec![
|
||||
Value::String("second".to_string()),
|
||||
Value::Boolean(true),
|
||||
]),
|
||||
]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest {
|
||||
first: "first".to_string(),
|
||||
second: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_field() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
struct DeriveTest {
|
||||
first: String,
|
||||
#[arma(default)]
|
||||
second: bool,
|
||||
}
|
||||
|
||||
let input = Value::Array(vec![Value::Array(vec![
|
||||
Value::String("first".to_string()),
|
||||
Value::String("first".to_string()),
|
||||
])]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest {
|
||||
first: "first".to_string(),
|
||||
second: false
|
||||
})
|
||||
);
|
||||
|
||||
let input = Value::Array(vec![
|
||||
Value::Array(vec![
|
||||
Value::String("first".to_string()),
|
||||
Value::String("first".to_string()),
|
||||
]),
|
||||
Value::Array(vec![
|
||||
Value::String("second".to_string()),
|
||||
Value::Boolean(true),
|
||||
]),
|
||||
]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest {
|
||||
first: "first".to_string(),
|
||||
second: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_field_precedence() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
#[arma(default)]
|
||||
struct DeriveTest {
|
||||
first: String,
|
||||
#[arma(default)]
|
||||
second: bool,
|
||||
}
|
||||
|
||||
impl Default for DeriveTest {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
first: "first".to_string(),
|
||||
second: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let input = Value::Array(vec![]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest {
|
||||
first: "first".to_string(),
|
||||
second: false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_missing() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
struct DeriveTest {
|
||||
_expected: String,
|
||||
}
|
||||
|
||||
let input = Value::Array(vec![]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Err(FromArmaError::MissingField("_expected".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_unknown() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
struct DeriveTest {
|
||||
_expected: String,
|
||||
}
|
||||
|
||||
let input = Value::Array(vec![
|
||||
Value::Array(vec![
|
||||
Value::String("_expected".to_string()),
|
||||
Value::String("_expected".to_string()),
|
||||
]),
|
||||
Value::Array(vec![
|
||||
Value::String("unknown".to_string()),
|
||||
Value::String("unknown".to_string()),
|
||||
]),
|
||||
]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Err(FromArmaError::UnknownField("unknown".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_duplicate() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
struct DeriveTest {
|
||||
_expected: String,
|
||||
}
|
||||
|
||||
let input = Value::Array(vec![
|
||||
Value::Array(vec![
|
||||
Value::String("_expected".to_string()),
|
||||
Value::String("first".to_string()),
|
||||
]),
|
||||
Value::Array(vec![
|
||||
Value::String("_expected".to_string()),
|
||||
Value::String("second".to_string()),
|
||||
]),
|
||||
]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Err(FromArmaError::DuplicateField("_expected".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_error_unknown() {
|
||||
#[derive(FromArma, Default, Debug, PartialEq)]
|
||||
#[arma(default)]
|
||||
struct DeriveTest {
|
||||
_expected: String,
|
||||
}
|
||||
|
||||
let input = Value::Array(vec![Value::Array(vec![
|
||||
Value::String("unknown".to_string()),
|
||||
Value::String("unknown".to_string()),
|
||||
])]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Err(FromArmaError::UnknownField("unknown".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_field_error_missing() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
struct DeriveTest {
|
||||
_expected: String,
|
||||
#[arma(default)]
|
||||
_default: String,
|
||||
}
|
||||
|
||||
let input = Value::Array(vec![]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Err(FromArmaError::MissingField("_expected".to_string()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod tuple {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn derive() {
|
||||
#[derive(FromArma, IntoArma, Debug, PartialEq)]
|
||||
struct DeriveTest(String, bool);
|
||||
|
||||
let serialized = DeriveTest("first".to_string(), true);
|
||||
let deserialized = Value::Array(vec![
|
||||
Value::String("first".to_string()),
|
||||
Value::Boolean(true),
|
||||
]);
|
||||
assert_eq!(serialized.to_arma(), deserialized);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(deserialized.to_string()),
|
||||
Ok(serialized)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_string_field() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
struct DeriveTest(String, #[arma(from_str)] ValueStringImpl);
|
||||
|
||||
let input = Value::Array(vec![
|
||||
Value::String("first".to_string()),
|
||||
Value::String("odd".to_string()),
|
||||
]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest("first".to_string(), ValueStringImpl::Odd))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string_field() {
|
||||
#[derive(IntoArma, Debug, PartialEq)]
|
||||
struct DeriveTest(String, #[arma(to_string)] ValueStringImpl);
|
||||
|
||||
assert_eq!(
|
||||
DeriveTest("first".to_string(), ValueStringImpl::Odd).to_arma(),
|
||||
Value::Array(vec![
|
||||
Value::String("first".to_string()),
|
||||
Value::String("odd".to_string()),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str_default_field() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
struct DeriveTest(String, #[arma(from_str, default)] ValueStringImpl);
|
||||
|
||||
let input = Value::Array(vec![Value::String("first".to_string())]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest("first".to_string(), ValueStringImpl::default()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
#[derive(FromArma, Default, Debug, PartialEq)]
|
||||
#[arma(default)]
|
||||
struct DeriveTest(String, bool);
|
||||
|
||||
let input = Value::Array(vec![]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest::default())
|
||||
);
|
||||
|
||||
let input = Value::Array(vec![Value::String("first".to_string())]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest("first".to_string(), false))
|
||||
);
|
||||
|
||||
let input = Value::Array(vec![
|
||||
Value::String("first".to_string()),
|
||||
Value::Boolean(true),
|
||||
]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest("first".to_string(), true))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_field() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
struct DeriveTest(String, #[arma(default)] bool);
|
||||
|
||||
let input = Value::Array(vec![Value::String("first".to_string())]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest("first".to_string(), false))
|
||||
);
|
||||
|
||||
let input = Value::Array(vec![
|
||||
Value::String("first".to_string()),
|
||||
Value::Boolean(true),
|
||||
]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest("first".to_string(), true))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_multi_field() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
struct DeriveTest(String, #[arma(default)] bool, #[arma(default)] bool);
|
||||
|
||||
let input = Value::Array(vec![Value::String("first".to_string())]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest("first".to_string(), false, false))
|
||||
);
|
||||
|
||||
let input = Value::Array(vec![
|
||||
Value::String("first".to_string()),
|
||||
Value::Boolean(true),
|
||||
]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest("first".to_string(), true, false))
|
||||
);
|
||||
|
||||
let input = Value::Array(vec![
|
||||
Value::String("first".to_string()),
|
||||
Value::Boolean(true),
|
||||
Value::Boolean(true),
|
||||
]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest("first".to_string(), true, true))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_field_precedence() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
#[arma(default)]
|
||||
struct DeriveTest(String, #[arma(default)] bool);
|
||||
|
||||
impl Default for DeriveTest {
|
||||
fn default() -> Self {
|
||||
Self("first".to_string(), true)
|
||||
}
|
||||
}
|
||||
|
||||
let input = Value::Array(vec![]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest("first".to_string(), false))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_length() {
|
||||
#[derive(FromArma, Default, Debug, PartialEq)]
|
||||
struct DeriveTest(String, String);
|
||||
|
||||
let input = Value::Array(vec![
|
||||
Value::String("first".to_string()),
|
||||
Value::String("second".to_string()),
|
||||
Value::String("third".to_string()),
|
||||
Value::String("forth".to_string()),
|
||||
]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Err(arma_rs::FromArmaError::InvalidLength {
|
||||
expected: 2,
|
||||
actual: 4,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_field_error() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
struct DeriveTest(String, #[arma(default)] String);
|
||||
|
||||
let input = Value::Array(vec![]);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Err(arma_rs::FromArmaError::InvalidLength {
|
||||
expected: 2,
|
||||
actual: 0,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod newtype {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn derive() {
|
||||
#[derive(FromArma, IntoArma, Debug, PartialEq)]
|
||||
struct DeriveTest(String);
|
||||
|
||||
let serialized = DeriveTest("expected".to_string());
|
||||
let deserialized = Value::String("expected".to_string());
|
||||
assert_eq!(serialized.to_arma(), deserialized);
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(deserialized.to_string()),
|
||||
Ok(serialized)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
#[derive(FromArma, Debug, PartialEq)]
|
||||
struct DeriveTest(#[arma(from_str)] ValueStringImpl);
|
||||
|
||||
let input = Value::String("odd".to_string());
|
||||
assert_eq!(
|
||||
DeriveTest::from_arma(input.to_string()),
|
||||
Ok(DeriveTest(ValueStringImpl::Odd))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
#[derive(IntoArma, Debug, PartialEq)]
|
||||
struct DeriveTest(#[arma(to_string)] ValueStringImpl);
|
||||
|
||||
assert_eq!(
|
||||
DeriveTest(ValueStringImpl::Odd).to_arma(),
|
||||
Value::String("odd".to_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
6
vendor/arma-rs/tests/derive/fail_enum.rs
vendored
Normal file
6
vendor/arma-rs/tests/derive/fail_enum.rs
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
use arma_rs::{FromArma, IntoArma};
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
enum DeriveTest {}
|
||||
|
||||
fn main() {}
|
||||
15
vendor/arma-rs/tests/derive/fail_enum.stderr
vendored
Normal file
15
vendor/arma-rs/tests/derive/fail_enum.stderr
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
error: enums aren't supported
|
||||
--> tests/derive/fail_enum.rs:3:10
|
||||
|
|
||||
3 | #[derive(FromArma, IntoArma)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `FromArma` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: enums aren't supported
|
||||
--> tests/derive/fail_enum.rs:3:20
|
||||
|
|
||||
3 | #[derive(FromArma, IntoArma)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `IntoArma` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
65
vendor/arma-rs/tests/derive/fail_struct_attributes.rs
vendored
Normal file
65
vendor/arma-rs/tests/derive/fail_struct_attributes.rs
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
use arma_rs::{FromArma, IntoArma};
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
#[arma]
|
||||
struct NoList {
|
||||
test: u32,
|
||||
}
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
#[arma("literal")]
|
||||
struct Literal {
|
||||
test: u32,
|
||||
}
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
#[arma(unknown)]
|
||||
struct Unknown {
|
||||
test: u32,
|
||||
}
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
#[arma(unknown::path)]
|
||||
struct UnknownPath {
|
||||
test: u32,
|
||||
}
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
#[arma(default, default)]
|
||||
struct Duplicate {
|
||||
test: u32,
|
||||
}
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
#[arma(default)]
|
||||
#[arma(default)]
|
||||
struct StackedDuplicate {
|
||||
test: u32,
|
||||
}
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
struct FieldUnknown {
|
||||
#[arma(unknown)]
|
||||
test: u32,
|
||||
}
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
struct FieldDuplicate {
|
||||
#[arma(default, default)]
|
||||
test: u32,
|
||||
}
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
struct FieldStackedDuplicate {
|
||||
#[arma(default)]
|
||||
#[arma(default)]
|
||||
test: u32,
|
||||
}
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
#[arma(unknown, default, default)]
|
||||
struct MultipleErrors {
|
||||
test: u32,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
65
vendor/arma-rs/tests/derive/fail_struct_attributes.stderr
vendored
Normal file
65
vendor/arma-rs/tests/derive/fail_struct_attributes.stderr
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
error: expected attribute arguments in parentheses: #[arma(...)]
|
||||
--> tests/derive/fail_struct_attributes.rs:4:3
|
||||
|
|
||||
4 | #[arma]
|
||||
| ^^^^
|
||||
|
||||
error: unexpected literal in nested attribute, expected ident
|
||||
--> tests/derive/fail_struct_attributes.rs:10:8
|
||||
|
|
||||
10 | #[arma("literal")]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: unknown arma container attribute `unknown`
|
||||
--> tests/derive/fail_struct_attributes.rs:16:8
|
||||
|
|
||||
16 | #[arma(unknown)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: unknown arma container attribute `unknown::path`
|
||||
--> tests/derive/fail_struct_attributes.rs:22:8
|
||||
|
|
||||
22 | #[arma(unknown::path)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: duplicate arma attribute `default`
|
||||
--> tests/derive/fail_struct_attributes.rs:28:17
|
||||
|
|
||||
28 | #[arma(default, default)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: duplicate arma attribute `default`
|
||||
--> tests/derive/fail_struct_attributes.rs:35:8
|
||||
|
|
||||
35 | #[arma(default)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: unknown arma field attribute `unknown`
|
||||
--> tests/derive/fail_struct_attributes.rs:42:12
|
||||
|
|
||||
42 | #[arma(unknown)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: duplicate arma attribute `default`
|
||||
--> tests/derive/fail_struct_attributes.rs:48:21
|
||||
|
|
||||
48 | #[arma(default, default)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: duplicate arma attribute `default`
|
||||
--> tests/derive/fail_struct_attributes.rs:55:12
|
||||
|
|
||||
55 | #[arma(default)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: unknown arma container attribute `unknown`
|
||||
--> tests/derive/fail_struct_attributes.rs:60:8
|
||||
|
|
||||
60 | #[arma(unknown, default, default)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: duplicate arma attribute `default`
|
||||
--> tests/derive/fail_struct_attributes.rs:60:26
|
||||
|
|
||||
60 | #[arma(unknown, default, default)]
|
||||
| ^^^^^^^
|
||||
16
vendor/arma-rs/tests/derive/fail_struct_map_transparent_default.rs
vendored
Normal file
16
vendor/arma-rs/tests/derive/fail_struct_map_transparent_default.rs
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
use arma_rs::{FromArma, IntoArma};
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
#[arma(transparent, default)]
|
||||
struct Container {
|
||||
first: String,
|
||||
}
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
#[arma(transparent)]
|
||||
struct Field {
|
||||
#[arma(default)]
|
||||
first: String,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
11
vendor/arma-rs/tests/derive/fail_struct_map_transparent_default.stderr
vendored
Normal file
11
vendor/arma-rs/tests/derive/fail_struct_map_transparent_default.stderr
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
error: #[arma(default)] and #[arma(transparent)] cannot be used together
|
||||
--> tests/derive/fail_struct_map_transparent_default.rs:4:21
|
||||
|
|
||||
4 | #[arma(transparent, default)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: #[arma(default)] and #[arma(transparent)] cannot be used together
|
||||
--> tests/derive/fail_struct_map_transparent_default.rs:12:12
|
||||
|
|
||||
12 | #[arma(default)]
|
||||
| ^^^^^^^
|
||||
10
vendor/arma-rs/tests/derive/fail_struct_map_transparent_multi.rs
vendored
Normal file
10
vendor/arma-rs/tests/derive/fail_struct_map_transparent_multi.rs
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
use arma_rs::{FromArma, IntoArma};
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
#[arma(transparent)]
|
||||
struct TooManyFields {
|
||||
first: String,
|
||||
second: String,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
5
vendor/arma-rs/tests/derive/fail_struct_map_transparent_multi.stderr
vendored
Normal file
5
vendor/arma-rs/tests/derive/fail_struct_map_transparent_multi.stderr
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
error: #[arma(transparent)] structs must have exactly one field
|
||||
--> tests/derive/fail_struct_map_transparent_multi.rs:4:8
|
||||
|
|
||||
4 | #[arma(transparent)]
|
||||
| ^^^^^^^^^^^
|
||||
10
vendor/arma-rs/tests/derive/fail_struct_newtype_default.rs
vendored
Normal file
10
vendor/arma-rs/tests/derive/fail_struct_newtype_default.rs
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
use arma_rs::{FromArma, IntoArma};
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
#[arma(default)]
|
||||
struct Container(u32);
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
struct Field(#[arma(default)] u32);
|
||||
|
||||
fn main() {}
|
||||
11
vendor/arma-rs/tests/derive/fail_struct_newtype_default.stderr
vendored
Normal file
11
vendor/arma-rs/tests/derive/fail_struct_newtype_default.stderr
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
error: #[arma(default)] cannot be used on new type structs
|
||||
--> tests/derive/fail_struct_newtype_default.rs:4:8
|
||||
|
|
||||
4 | #[arma(default)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: #[arma(default)] cannot be used on new type structs
|
||||
--> tests/derive/fail_struct_newtype_default.rs:8:21
|
||||
|
|
||||
8 | struct Field(#[arma(default)] u32);
|
||||
| ^^^^^^^
|
||||
6
vendor/arma-rs/tests/derive/fail_struct_tuple_default_field_first.rs
vendored
Normal file
6
vendor/arma-rs/tests/derive/fail_struct_tuple_default_field_first.rs
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
use arma_rs::{FromArma, IntoArma};
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
struct DeriveTest(#[arma(default)] String, u32, bool);
|
||||
|
||||
fn main() {}
|
||||
11
vendor/arma-rs/tests/derive/fail_struct_tuple_default_field_first.stderr
vendored
Normal file
11
vendor/arma-rs/tests/derive/fail_struct_tuple_default_field_first.stderr
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
error: field must have #[arma(default)] because previous field 0 has #[arma(default)]
|
||||
--> tests/derive/fail_struct_tuple_default_field_first.rs:4:44
|
||||
|
|
||||
4 | struct DeriveTest(#[arma(default)] String, u32, bool);
|
||||
| ^^^
|
||||
|
||||
error: field must have #[arma(default)] because previous field 0 has #[arma(default)]
|
||||
--> tests/derive/fail_struct_tuple_default_field_first.rs:4:49
|
||||
|
|
||||
4 | struct DeriveTest(#[arma(default)] String, u32, bool);
|
||||
| ^^^^
|
||||
7
vendor/arma-rs/tests/derive/fail_struct_tuple_transparent.rs
vendored
Normal file
7
vendor/arma-rs/tests/derive/fail_struct_tuple_transparent.rs
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
use arma_rs::{FromArma, IntoArma};
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
#[arma(transparent)]
|
||||
struct DeriveTest(String, u32);
|
||||
|
||||
fn main() {}
|
||||
5
vendor/arma-rs/tests/derive/fail_struct_tuple_transparent.stderr
vendored
Normal file
5
vendor/arma-rs/tests/derive/fail_struct_tuple_transparent.stderr
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
error: #[arma(transparent)] cannot be used on tuple like structs
|
||||
--> tests/derive/fail_struct_tuple_transparent.rs:4:8
|
||||
|
|
||||
4 | #[arma(transparent)]
|
||||
| ^^^^^^^^^^^
|
||||
12
vendor/arma-rs/tests/derive/fail_struct_unit.rs
vendored
Normal file
12
vendor/arma-rs/tests/derive/fail_struct_unit.rs
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
use arma_rs::{FromArma, IntoArma};
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
struct Unit;
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
struct EmptyMap {}
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
struct EmptyTuple();
|
||||
|
||||
fn main() {}
|
||||
47
vendor/arma-rs/tests/derive/fail_struct_unit.stderr
vendored
Normal file
47
vendor/arma-rs/tests/derive/fail_struct_unit.stderr
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
error: unit-like structs aren't supported
|
||||
--> tests/derive/fail_struct_unit.rs:3:10
|
||||
|
|
||||
3 | #[derive(FromArma, IntoArma)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `FromArma` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unit-like structs aren't supported
|
||||
--> tests/derive/fail_struct_unit.rs:3:20
|
||||
|
|
||||
3 | #[derive(FromArma, IntoArma)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `IntoArma` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unit-like structs aren't supported
|
||||
--> tests/derive/fail_struct_unit.rs:6:10
|
||||
|
|
||||
6 | #[derive(FromArma, IntoArma)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `FromArma` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unit-like structs aren't supported
|
||||
--> tests/derive/fail_struct_unit.rs:6:20
|
||||
|
|
||||
6 | #[derive(FromArma, IntoArma)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `IntoArma` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unit-like structs aren't supported
|
||||
--> tests/derive/fail_struct_unit.rs:9:10
|
||||
|
|
||||
9 | #[derive(FromArma, IntoArma)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `FromArma` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unit-like structs aren't supported
|
||||
--> tests/derive/fail_struct_unit.rs:9:20
|
||||
|
|
||||
9 | #[derive(FromArma, IntoArma)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `IntoArma` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
8
vendor/arma-rs/tests/derive/fail_union.rs
vendored
Normal file
8
vendor/arma-rs/tests/derive/fail_union.rs
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
use arma_rs::{FromArma, IntoArma};
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
union DeriveTest {
|
||||
first: u32,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
15
vendor/arma-rs/tests/derive/fail_union.stderr
vendored
Normal file
15
vendor/arma-rs/tests/derive/fail_union.stderr
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
error: unions aren't supported
|
||||
--> tests/derive/fail_union.rs:3:10
|
||||
|
|
||||
3 | #[derive(FromArma, IntoArma)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `FromArma` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unions aren't supported
|
||||
--> tests/derive/fail_union.rs:3:20
|
||||
|
|
||||
3 | #[derive(FromArma, IntoArma)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `IntoArma` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
21
vendor/arma-rs/tests/derive/pass_struct_custom_impls.rs
vendored
Normal file
21
vendor/arma-rs/tests/derive/pass_struct_custom_impls.rs
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
use arma_rs::{FromArma, FromArmaError, IntoArma, Value};
|
||||
|
||||
#[derive(IntoArma)]
|
||||
struct CustomFrom(u32);
|
||||
|
||||
impl FromArma for CustomFrom {
|
||||
fn from_arma(_: String) -> Result<Self, FromArmaError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromArma)]
|
||||
struct CustomInto(u32);
|
||||
|
||||
impl IntoArma for CustomInto {
|
||||
fn to_arma(&self) -> Value {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
22
vendor/arma-rs/tests/derive/pass_struct_generics.rs
vendored
Normal file
22
vendor/arma-rs/tests/derive/pass_struct_generics.rs
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
use arma_rs::{FromArma, IntoArma};
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
struct Newtype<T: IntoArma + FromArma>(T);
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
struct Tuple<A, B>(A, B)
|
||||
where
|
||||
A: IntoArma + FromArma,
|
||||
B: IntoArma + FromArma;
|
||||
|
||||
#[derive(FromArma, IntoArma)]
|
||||
struct Map<A, B>
|
||||
where
|
||||
A: IntoArma + FromArma,
|
||||
B: IntoArma + FromArma,
|
||||
{
|
||||
first: A,
|
||||
second: B,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
485
vendor/arma-rs/tests/emulate.rs
vendored
Normal file
485
vendor/arma-rs/tests/emulate.rs
vendored
Normal file
@@ -0,0 +1,485 @@
|
||||
#[cfg(feature = "extension")]
|
||||
mod extension {
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{CStr, CString},
|
||||
sync::{Arc, Once, RwLock},
|
||||
};
|
||||
|
||||
use arma_rs::{CallbackError, Context, Extension};
|
||||
|
||||
macro_rules! platform_extern {
|
||||
($($func_body:tt)*) => {
|
||||
#[cfg(windows)]
|
||||
extern "stdcall" $($func_body)*
|
||||
#[cfg(not(windows))]
|
||||
extern "C" $($func_body)*
|
||||
};
|
||||
}
|
||||
|
||||
type Stack = Arc<RwLock<HashMap<String, Vec<(String, String, String)>>>>;
|
||||
|
||||
static mut CALLBACK_STACK: Option<Stack> = None;
|
||||
static CALLBACK_STACK_INIT: Once = Once::new();
|
||||
|
||||
fn get_callback_stack() -> Stack {
|
||||
unsafe {
|
||||
CALLBACK_STACK_INIT.call_once(|| {
|
||||
CALLBACK_STACK = Some(Arc::new(RwLock::new(HashMap::new())));
|
||||
});
|
||||
CALLBACK_STACK.as_ref().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn callback_handler(scope: String, name: *const i8, func: *const i8, data: *const i8) -> i32 {
|
||||
let stack = get_callback_stack();
|
||||
unsafe {
|
||||
let name = CStr::from_ptr(name).to_str().unwrap().to_string();
|
||||
let func = CStr::from_ptr(func).to_str().unwrap().to_string();
|
||||
let data = CStr::from_ptr(data).to_str().unwrap().to_string();
|
||||
let mut stack = stack.write().unwrap();
|
||||
stack.entry(scope).or_default().push((name, func, data));
|
||||
}
|
||||
2
|
||||
}
|
||||
|
||||
mod c_interface_full {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn extension() {
|
||||
let mut extension = Extension::build()
|
||||
.command("hello", || -> &'static str { "Hello" })
|
||||
.command("welcome", |name: String| -> String {
|
||||
format!("Welcome {name}")
|
||||
})
|
||||
.command(
|
||||
"callback",
|
||||
|ctx: Context, id: String| -> Result<(), CallbackError> {
|
||||
ctx.callback_data("callback", "fired", id)
|
||||
},
|
||||
)
|
||||
.finish();
|
||||
platform_extern!(
|
||||
fn callback(name: *const i8, func: *const i8, data: *const i8) -> i32 {
|
||||
callback_handler("c_interface_full".to_string(), name, func, data)
|
||||
}
|
||||
);
|
||||
extension.register_callback(callback);
|
||||
extension.run_callbacks();
|
||||
let stack = get_callback_stack();
|
||||
assert_eq!(stack.read().unwrap().get("c_interface_full"), None);
|
||||
unsafe {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new("callback").unwrap().into_raw();
|
||||
let ptr_hello = CString::new("\"hello\"").unwrap().into_raw();
|
||||
let code = extension.handle_call(
|
||||
ptr,
|
||||
output.as_mut_ptr(),
|
||||
1024,
|
||||
Some(vec![ptr_hello].as_mut_ptr()),
|
||||
Some(1),
|
||||
true,
|
||||
);
|
||||
assert_eq!(code, 0);
|
||||
let _ = CString::from_raw(ptr);
|
||||
let _ = CString::from_raw(ptr_hello);
|
||||
};
|
||||
unsafe {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new("hello").unwrap().into_raw();
|
||||
extension.handle_call(ptr, output.as_mut_ptr(), 1024, None, None, true);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok("Hello"));
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
unsafe {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new("welcome").unwrap().into_raw();
|
||||
let ptr_john = CString::new("\"John\"").unwrap().into_raw();
|
||||
extension.handle_call(
|
||||
ptr,
|
||||
output.as_mut_ptr(),
|
||||
1024,
|
||||
Some(vec![ptr_john].as_mut_ptr()),
|
||||
Some(1),
|
||||
true,
|
||||
);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok("Welcome John"));
|
||||
let _ = CString::from_raw(ptr);
|
||||
let _ = CString::from_raw(ptr_john);
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
assert_eq!(
|
||||
stack.read().unwrap().get("c_interface_full").unwrap().len(),
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c_interface_builder() {
|
||||
let extension = Extension::build().finish();
|
||||
assert_eq!(extension.version(), "0.0.0");
|
||||
assert!(!extension.allow_no_args());
|
||||
|
||||
let extension = Extension::build().version("1.0.0".to_string()).finish();
|
||||
assert_eq!(extension.version(), "1.0.0".to_string());
|
||||
|
||||
let extension = Extension::build().allow_no_args().finish();
|
||||
assert!(extension.allow_no_args());
|
||||
}
|
||||
|
||||
mod c_interface_invalid_calls {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn extension() {
|
||||
let mut extension = Extension::build()
|
||||
.command(
|
||||
"callback_invalid_name",
|
||||
|ctx: Context| -> Result<(), CallbackError> {
|
||||
ctx.callback_null("call\0back", "fired")
|
||||
},
|
||||
)
|
||||
.command(
|
||||
"callback_invalid_func",
|
||||
|ctx: Context| -> Result<(), CallbackError> {
|
||||
ctx.callback_null("callback", "fir\0ed")
|
||||
},
|
||||
)
|
||||
.command(
|
||||
"callback_invalid_data",
|
||||
|ctx: Context| -> Result<(), CallbackError> {
|
||||
ctx.callback_data("callback", "fired", "dat\0a")
|
||||
},
|
||||
)
|
||||
.command(
|
||||
"callback_valid_null",
|
||||
|ctx: Context| -> Result<(), CallbackError> {
|
||||
ctx.callback_null("callback", "fired")
|
||||
},
|
||||
)
|
||||
.command(
|
||||
"callback_valid_data",
|
||||
|ctx: Context| -> Result<(), CallbackError> {
|
||||
ctx.callback_data("callback", "fired", "data")
|
||||
},
|
||||
)
|
||||
.finish();
|
||||
platform_extern!(
|
||||
fn callback(name: *const i8, func: *const i8, data: *const i8) -> i32 {
|
||||
callback_handler("c_interface_invalid_calls".to_string(), name, func, data)
|
||||
}
|
||||
);
|
||||
extension.register_callback(callback);
|
||||
extension.run_callbacks();
|
||||
let ptr = CString::new("hello").unwrap().into_raw();
|
||||
unsafe {
|
||||
let mut output = [0i8; 1024];
|
||||
let code = extension.handle_call(ptr, output.as_mut_ptr(), 1024, None, None, true);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok(""));
|
||||
assert_eq!(code, 1);
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
|
||||
// Unknown function name
|
||||
unsafe {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new("invalid").unwrap().into_raw();
|
||||
let code = extension.handle_call(ptr, output.as_mut_ptr(), 1024, None, None, true);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok(""));
|
||||
assert_eq!(code, 1);
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
|
||||
// Invalid callback name
|
||||
unsafe {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new("callback_invalid_name").unwrap().into_raw();
|
||||
let code = extension.handle_call(ptr, output.as_mut_ptr(), 1024, None, None, true);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok("null"));
|
||||
assert_eq!(code, 0);
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
|
||||
// Invalid callback func
|
||||
unsafe {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new("callback_invalid_func").unwrap().into_raw();
|
||||
let code = extension.handle_call(ptr, output.as_mut_ptr(), 1024, None, None, true);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok("null"));
|
||||
assert_eq!(code, 0);
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
|
||||
// Invalid callback data
|
||||
unsafe {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new("callback_invalid_data").unwrap().into_raw();
|
||||
let code = extension.handle_call(ptr, output.as_mut_ptr(), 1024, None, None, true);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok("null"));
|
||||
assert_eq!(code, 0);
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
|
||||
// Valid null callback
|
||||
unsafe {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new("callback_valid_null").unwrap().into_raw();
|
||||
let code = extension.handle_call(ptr, output.as_mut_ptr(), 1024, None, None, true);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok("null"));
|
||||
assert_eq!(code, 0);
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
|
||||
// Valid data callback
|
||||
unsafe {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new("callback_valid_data").unwrap().into_raw();
|
||||
let code = extension.handle_call(ptr, output.as_mut_ptr(), 1024, None, None, true);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok("null"));
|
||||
assert_eq!(code, 0);
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
let stack = get_callback_stack();
|
||||
assert_eq!(
|
||||
stack
|
||||
.read()
|
||||
.unwrap()
|
||||
.get("c_interface_invalid_calls")
|
||||
.unwrap()
|
||||
.len(),
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c_interface_errors() {
|
||||
let extension = Extension::build()
|
||||
.command("add_no_context", |a: i32, b: i32| {
|
||||
let _ = a + b;
|
||||
})
|
||||
.command("add_no_context_return", |a: i32, b: i32| a + b)
|
||||
.command("add_context", |_ctx: Context, a: i32, b: i32| {
|
||||
let _ = a + b;
|
||||
})
|
||||
.command("add_context_return", |_ctx: Context, a: i32, b: i32| a + b)
|
||||
.command("overflow", |ctx: Context| "X".repeat(ctx.buffer_len() + 1))
|
||||
.command("result", |error: bool| -> Result<String, String> {
|
||||
if error {
|
||||
Err(String::from("told to error"))
|
||||
} else {
|
||||
Ok(String::from("told to succeed"))
|
||||
}
|
||||
})
|
||||
.finish();
|
||||
|
||||
// Valid
|
||||
unsafe {
|
||||
let ptr1 = CString::new("1").unwrap().into_raw();
|
||||
let ptr2 = CString::new("2").unwrap().into_raw();
|
||||
for (func, result) in [
|
||||
("add_no_context", "null"),
|
||||
("add_no_context_return", "3"),
|
||||
("add_context", "null"),
|
||||
("add_context_return", "3"),
|
||||
] {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new(func).unwrap().into_raw();
|
||||
let code = extension.handle_call(
|
||||
ptr,
|
||||
output.as_mut_ptr(),
|
||||
1024,
|
||||
Some(vec![ptr1, ptr2].as_mut_ptr()),
|
||||
Some(2),
|
||||
true,
|
||||
);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok(result));
|
||||
assert_eq!(code, 0);
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
let _ = CString::from_raw(ptr1);
|
||||
let _ = CString::from_raw(ptr2);
|
||||
}
|
||||
|
||||
// Invalid too many arguments
|
||||
unsafe {
|
||||
let ptr1 = CString::new("1").unwrap().into_raw();
|
||||
let ptr2 = CString::new("2").unwrap().into_raw();
|
||||
let ptr3 = CString::new("3").unwrap().into_raw();
|
||||
for func in [
|
||||
"add_no_context",
|
||||
"add_no_context_return",
|
||||
"add_context",
|
||||
"add_context_return",
|
||||
] {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new(func).unwrap().into_raw();
|
||||
let code = extension.handle_call(
|
||||
ptr,
|
||||
output.as_mut_ptr(),
|
||||
1024,
|
||||
Some(vec![ptr1, ptr2, ptr3].as_mut_ptr()),
|
||||
Some(3),
|
||||
true,
|
||||
);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok(""));
|
||||
assert_eq!(code, 23);
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
let _ = CString::from_raw(ptr1);
|
||||
let _ = CString::from_raw(ptr2);
|
||||
let _ = CString::from_raw(ptr3);
|
||||
}
|
||||
|
||||
// Invalid too few arguments
|
||||
unsafe {
|
||||
let ptr1 = CString::new("1").unwrap().into_raw();
|
||||
for func in [
|
||||
"add_no_context",
|
||||
"add_no_context_return",
|
||||
"add_context",
|
||||
"add_context_return",
|
||||
] {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new(func).unwrap().into_raw();
|
||||
let code = extension.handle_call(
|
||||
ptr,
|
||||
output.as_mut_ptr(),
|
||||
1024,
|
||||
Some(vec![ptr1].as_mut_ptr()),
|
||||
Some(1),
|
||||
true,
|
||||
);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok(""));
|
||||
assert_eq!(code, 21);
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
let _ = CString::from_raw(ptr1);
|
||||
}
|
||||
|
||||
// Valid type conversion
|
||||
unsafe {
|
||||
let ptr1 = CString::new("1").unwrap().into_raw();
|
||||
let ptr2 = CString::new("\"2\"").unwrap().into_raw();
|
||||
for (func, result) in [
|
||||
("add_no_context", "null"),
|
||||
("add_no_context_return", "3"),
|
||||
("add_context", "null"),
|
||||
("add_context_return", "3"),
|
||||
] {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new(func).unwrap().into_raw();
|
||||
let code = extension.handle_call(
|
||||
ptr,
|
||||
output.as_mut_ptr(),
|
||||
1024,
|
||||
Some(vec![ptr1, ptr2].as_mut_ptr()),
|
||||
Some(2),
|
||||
true,
|
||||
);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok(result));
|
||||
assert_eq!(code, 0);
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
let _ = CString::from_raw(ptr1);
|
||||
let _ = CString::from_raw(ptr2);
|
||||
}
|
||||
|
||||
// Invalid type
|
||||
unsafe {
|
||||
let ptr1 = CString::new("1").unwrap().into_raw();
|
||||
let ptr2 = CString::new("\"two\"").unwrap().into_raw();
|
||||
for func in [
|
||||
"add_no_context",
|
||||
"add_no_context_return",
|
||||
"add_context",
|
||||
"add_context_return",
|
||||
] {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new(func).unwrap().into_raw();
|
||||
let code = extension.handle_call(
|
||||
ptr,
|
||||
output.as_mut_ptr(),
|
||||
1024,
|
||||
Some(vec![ptr1, ptr2].as_mut_ptr()),
|
||||
Some(2),
|
||||
true,
|
||||
);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok(""));
|
||||
assert_eq!(code, 31);
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
let _ = CString::from_raw(ptr1);
|
||||
let _ = CString::from_raw(ptr2);
|
||||
}
|
||||
|
||||
// Overflow
|
||||
unsafe {
|
||||
let ptr = CString::new("overflow").unwrap().into_raw();
|
||||
let mut output = [0i8; 1024];
|
||||
let code = extension.handle_call(ptr, output.as_mut_ptr(), 1024, None, None, true);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok(""));
|
||||
assert_eq!(code, 4);
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
|
||||
// Result - true
|
||||
unsafe {
|
||||
let mut output = [0i8; 1024];
|
||||
let ptr = CString::new("result").unwrap().into_raw();
|
||||
let ptr_true = CString::new("true").unwrap().into_raw();
|
||||
let code = extension.handle_call(
|
||||
ptr,
|
||||
output.as_mut_ptr(),
|
||||
1024,
|
||||
Some(vec![ptr_true].as_mut_ptr()),
|
||||
Some(1),
|
||||
true,
|
||||
);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok("told to error"));
|
||||
assert_eq!(code, 9);
|
||||
let _ = CString::from_raw(ptr);
|
||||
let _ = CString::from_raw(ptr_true);
|
||||
}
|
||||
|
||||
// Result - false
|
||||
unsafe {
|
||||
let ptr = CString::new("result").unwrap().into_raw();
|
||||
let ptr_false = CString::new("false").unwrap().into_raw();
|
||||
let mut output = [0i8; 1024];
|
||||
let code = extension.handle_call(
|
||||
ptr,
|
||||
output.as_mut_ptr(),
|
||||
1024,
|
||||
Some(vec![ptr_false].as_mut_ptr()),
|
||||
Some(1),
|
||||
true,
|
||||
);
|
||||
let cstring = CStr::from_ptr(output.as_ptr()).to_str();
|
||||
assert_eq!(cstring, Ok("told to succeed"));
|
||||
assert_eq!(code, 0);
|
||||
let _ = CString::from_raw(ptr);
|
||||
let _ = CString::from_raw(ptr_false);
|
||||
}
|
||||
}
|
||||
}
|
||||
367
vendor/arma-rs/tests/main.rs
vendored
Normal file
367
vendor/arma-rs/tests/main.rs
vendored
Normal file
@@ -0,0 +1,367 @@
|
||||
#[cfg(feature = "extension")]
|
||||
mod extension {
|
||||
use arma_rs::{Context, ContextState, Extension, Group};
|
||||
|
||||
#[test]
|
||||
fn root_command() {
|
||||
let extension = Extension::build()
|
||||
.command("hello", || -> &'static str { "Hello" })
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, _) = extension.call("hello", None);
|
||||
assert_eq!(result, "Hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_command_with_args() {
|
||||
let extension = Extension::build()
|
||||
.command("hello", |name: String| -> String {
|
||||
format!("Hello {name}")
|
||||
})
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, _) = extension.call("hello", Some(vec![String::from("John")]));
|
||||
assert_eq!(result, "Hello John");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_command_no_return() {
|
||||
let extension = Extension::build().command("nop", || {}).finish().testing();
|
||||
let (result, code) = extension.call("nop", None);
|
||||
assert_eq!(code, 0);
|
||||
assert_eq!(result, "null");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_command_with_args_no_return() {
|
||||
let extension = Extension::build()
|
||||
.command("nop", |_: i8| {})
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, code) = extension.call("nop", Some(vec![String::from("4")]));
|
||||
assert_eq!(code, 0);
|
||||
assert_eq!(result, "null");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_command() {
|
||||
let extension = Extension::build()
|
||||
.group(
|
||||
"english",
|
||||
Group::new().command("hello", || -> &'static str { "Hello" }),
|
||||
)
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, _) = extension.call("english:hello", None);
|
||||
assert_eq!(result, "Hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_command_with_args() {
|
||||
let extension = Extension::build()
|
||||
.group(
|
||||
"english",
|
||||
Group::new().command("hello", |name: String| -> String {
|
||||
format!("Hello {name}")
|
||||
}),
|
||||
)
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, _) = extension.call("english:hello", Some(vec![String::from("John")]));
|
||||
assert_eq!(result, "Hello John");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_group_command() {
|
||||
let extension = Extension::build()
|
||||
.group(
|
||||
"greeting",
|
||||
Group::new().group(
|
||||
"english",
|
||||
Group::new().command("hello", || -> &'static str { "Hello" }),
|
||||
),
|
||||
)
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, _) = extension.call("greeting:english:hello", None);
|
||||
assert_eq!(result, "Hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_group_command_with_args() {
|
||||
let extension = Extension::build()
|
||||
.group(
|
||||
"greeting",
|
||||
Group::new().group(
|
||||
"english",
|
||||
Group::new().command("hello", |name: String| -> String {
|
||||
format!("Hello {name}")
|
||||
}),
|
||||
),
|
||||
)
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, _) =
|
||||
extension.call("greeting:english:hello", Some(vec![String::from("John")]));
|
||||
assert_eq!(result, "Hello John");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_ok() {
|
||||
let extension = Extension::build()
|
||||
.command("result", || -> Result<&str, &str> { Ok("Ok") })
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, code) = extension.call("result", None);
|
||||
assert_eq!(code, 0);
|
||||
assert_eq!(result, "Ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_err() {
|
||||
let extension = Extension::build()
|
||||
.command("result", || -> Result<&str, &str> { Err("Err") })
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, code) = extension.call("result", None);
|
||||
assert_eq!(code, 9);
|
||||
assert_eq!(result, "Err");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_unit_ok() {
|
||||
let extension = Extension::build()
|
||||
.command("result", || -> Result<(), &str> { Ok(()) })
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, code) = extension.call("result", None);
|
||||
assert_eq!(code, 0);
|
||||
assert_eq!(result, "null");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_unit_err() {
|
||||
let extension = Extension::build()
|
||||
.command("result", || -> Result<&str, ()> { Err(()) })
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, code) = extension.call("result", None);
|
||||
assert_eq!(code, 9);
|
||||
assert_eq!(result, "null");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_unit_both() {
|
||||
let extension = Extension::build()
|
||||
.command("result", || -> Result<(), ()> { Ok(()) })
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, code) = extension.call("result", None);
|
||||
assert_eq!(code, 0);
|
||||
assert_eq!(result, "null");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_found() {
|
||||
let extension = Extension::build().finish().testing();
|
||||
let (result, code) = extension.call("hello", None);
|
||||
assert_eq!(code, 1);
|
||||
assert_eq!(result, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_arg_count() {
|
||||
let extension = Extension::build()
|
||||
.command("hello", || -> &'static str { "Hello" })
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, code) = extension.call("hello", Some(vec![String::from("John")]));
|
||||
assert_eq!(code, 21);
|
||||
assert_eq!(result, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_arg_type() {
|
||||
let extension = Extension::build()
|
||||
.command("hello", |_: i32| -> &'static str { "Hello" })
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, code) = extension.call("hello", Some(vec![String::from("John")]));
|
||||
assert_eq!(code, 30);
|
||||
assert_eq!(result, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_arg_type_position() {
|
||||
let extension = Extension::build()
|
||||
.command("hello", |_: String, _: i32| -> &'static str { "Hello" })
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, code) = extension.call(
|
||||
"hello",
|
||||
Some(vec![String::from("John"), String::from("John")]),
|
||||
);
|
||||
assert_eq!(code, 31);
|
||||
assert_eq!(result, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filled_output() {
|
||||
let extension = Extension::build()
|
||||
.command("hello", |ctx: Context| -> String {
|
||||
"X".repeat(ctx.buffer_len())
|
||||
})
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, _) = extension.call("hello", None);
|
||||
assert_eq!(result.len(), extension.context().buffer_len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filled_output_with_args() {
|
||||
let extension = Extension::build()
|
||||
.command("hello", |ctx: Context, item: String| -> String {
|
||||
item.repeat(ctx.buffer_len())
|
||||
})
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, _) = extension.call("hello", Some(vec![String::from('X')]));
|
||||
assert_eq!(result.len(), extension.context().buffer_len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_overflow() {
|
||||
let extension = Extension::build()
|
||||
.command("hello", |ctx: Context| -> String {
|
||||
"X".repeat(ctx.buffer_len() + 1)
|
||||
})
|
||||
.finish()
|
||||
.testing();
|
||||
let (_, code) = extension.call("hello", None);
|
||||
assert_eq!(code, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_overflow_with_args() {
|
||||
let extension = Extension::build()
|
||||
.command("hello", |ctx: Context, item: String| -> String {
|
||||
item.repeat(ctx.buffer_len() + 1)
|
||||
})
|
||||
.finish()
|
||||
.testing();
|
||||
let (_, code) = extension.call("hello", Some(vec![String::from('X')]));
|
||||
assert_eq!(code, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn application_error_ok() {
|
||||
let extension = Extension::build()
|
||||
.command("hello", || -> Result<&str, &str> { Ok("Ok") })
|
||||
.finish()
|
||||
.testing();
|
||||
let (_, code) = extension.call("hello", None);
|
||||
assert_eq!(code, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn application_error_err() {
|
||||
let extension = Extension::build()
|
||||
.command("hello", || -> Result<&str, &str> { Err("Error") })
|
||||
.finish()
|
||||
.testing();
|
||||
let (_, code) = extension.call("hello", None);
|
||||
assert_eq!(code, 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_build() {
|
||||
let extension = Extension::build()
|
||||
.state(String::from("foobar"))
|
||||
.finish()
|
||||
.testing();
|
||||
let value = extension.state().try_get::<String>();
|
||||
assert_eq!(value, Some(&String::from("foobar")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_new() {
|
||||
let extension = Extension::build()
|
||||
.command("new", |ctx: Context, new: String| ctx.global().set(new))
|
||||
.finish()
|
||||
.testing();
|
||||
|
||||
let (_, _) = extension.call("new", Some(vec![String::from("foobar")]));
|
||||
let value = extension.state().try_get::<String>();
|
||||
assert_eq!(value, Some(&String::from("foobar")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_freeze() {
|
||||
let extension = Extension::build()
|
||||
.command("new", |ctx: Context, new: String| ctx.global().set(new))
|
||||
.freeze_state()
|
||||
.finish()
|
||||
.testing();
|
||||
assert!(extension.state().is_frozen());
|
||||
|
||||
let (_, _) = extension.call("new", Some(vec![String::from("foobar")]));
|
||||
let value = extension.state().try_get::<String>();
|
||||
assert_eq!(value, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_change() {
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
let extension = Extension::build()
|
||||
.state(AtomicUsize::new(42))
|
||||
.command("set", |ctx: Context, new: usize| {
|
||||
ctx.global()
|
||||
.get::<AtomicUsize>()
|
||||
.expect("state not found")
|
||||
.store(new, Ordering::Relaxed)
|
||||
})
|
||||
.finish()
|
||||
.testing();
|
||||
|
||||
let (_, _) = extension.call("set", Some(vec![String::from("21")]));
|
||||
let value = extension
|
||||
.state()
|
||||
.get::<AtomicUsize>()
|
||||
.load(Ordering::Relaxed);
|
||||
assert_eq!(value, 21);
|
||||
}
|
||||
|
||||
mod call_context {
|
||||
use arma_rs::{CallContext, Caller, Extension, Mission, Server, Source};
|
||||
|
||||
#[test]
|
||||
fn call() {
|
||||
let extension = Extension::build()
|
||||
.command("call_ctx", |call_context: CallContext| -> String {
|
||||
format!(
|
||||
"{:?},{:?},{:?},{:?}",
|
||||
call_context.caller(),
|
||||
call_context.source(),
|
||||
call_context.mission(),
|
||||
call_context.server()
|
||||
)
|
||||
})
|
||||
.finish()
|
||||
.testing();
|
||||
let (result, _) = extension.call_with_context(
|
||||
"call_ctx",
|
||||
None,
|
||||
Caller::Steam(123),
|
||||
Source::Pbo(String::from("pbo")),
|
||||
Mission::Mission(String::from("mission")),
|
||||
Server::Multiplayer(String::from("server")),
|
||||
0,
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
"Steam(123),Pbo(\"pbo\"),Mission(\"mission\"),Multiplayer(\"server\")"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user