Functional Weave
Code in Python

net.port-list@1.0.0

impl/rust.rs

2,412 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::net_port::is_port;  ← from net.port ^1.0.0 · built alongside by fune
use std::collections::HashSet;

/// Spaces and tabs only: a newline inside a port list is a mistake, not padding.
fn trim_blank(s: &str) -> &str {
    s.trim_matches(|c| c == ' ' || c == '\t')
}

/// Whole ASCII digits with no leading zero: "80", not "080", "+80" or "٨٠".
fn is_number(s: &str) -> bool {
    !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit()) && (s.len() == 1 || !s.starts_with('0'))
}

fn port(token: &str) -> i64 {
    // More than five digits cannot be a port, and would overflow an i64 parse.
    let value = if token.len() > 5 { 0 } else { token.parse::<i64>().unwrap() };
    if !is_port(value) {
        panic!("port \"{}\" is out of range 1-65535", token);
    }
    value
}

/// Parse a port list such as "22,80,8000-8010" into port numbers: each port
/// once, in the order first given. Ranges are inclusive.
///
/// # Panics
/// Panics on an empty list or item, a malformed port or range, a port outside
/// 1..=65535, or a range that runs backwards.
pub fn parse_port_list(text: &str) -> Vec<i64> {
    if trim_blank(text).is_empty() {
        panic!("port list is empty");
    }
    let mut out = Vec::new();
    let mut seen = HashSet::new();
    let mut add = |p: i64| {
        if seen.insert(p) {
            out.push(p);
        }
    };
    for raw in text.split(',') {
        let item = trim_blank(raw);
        if item.is_empty() {
            panic!("empty item in port list");
        }
        if item.contains('-') {
            let ends: Vec<&str> = item.split('-').collect();
            if ends.len() != 2 || !is_number(ends[0]) || !is_number(ends[1]) {
                panic!("invalid port range \"{}\"", item);
            }
            let low = port(ends[0]);
            let high = port(ends[1]);
            if low > high {
                panic!("port range \"{}\" runs backwards", item);
            }
            for p in low..=high {
                add(p);
            }
        } else {
            if !is_number(item) {
                panic!("invalid port \"{}\"", item);
            }
            add(port(item));
        }
    }
    out
}

pub fn fune_vector(args: &[Value]) -> Value {
    let text = match &args[0] {
        Value::Str(s) => s.as_str(),
        _ => panic!("text must be a string"),
    };
    Value::Arr(parse_port_list(text).into_iter().map(Value::Int).collect())
}