use std::{
borrow::Cow,
error::Error,
fmt::{self, Write},
hash::{Hash, Hasher},
mem, slice,
vec::IntoIter,
};
use chrono::{NaiveDate, NaiveDateTime, NaiveTime, TimeDelta as ChronoTimeDelta};
use num_bigint::BigInt;
use num_traits::{ToPrimitive, Zero};
use crate::{
builtins::BuiltinsFunctions,
exceptions::ExcType,
file_mode::FileMode,
format::{FormatFloat, StringRepr, bytes_repr, format_offset_timedelta_repr, string_repr_fmt},
resource::ResourceError,
};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum MontyObject {
Ellipsis,
None,
Bool(bool),
Int(i64),
BigInt(BigInt),
Float(f64),
String(String),
Bytes(Vec<u8>),
List(Vec<Self>),
Tuple(Vec<Self>),
NamedTuple {
type_name: String,
field_names: Vec<String>,
values: Vec<Self>,
},
Dict(DictPairs),
Set(Vec<Self>),
FrozenSet(Vec<Self>),
Date(MontyDate),
DateTime(MontyDateTime),
TimeDelta(MontyTimeDelta),
TimeZone(MontyTimeZone),
Exception {
exc_type: ExcType,
arg: Option<String>,
},
Type(MontyType),
BuiltinFunction(BuiltinsFunctions),
Path(String),
FileHandle(MontyFileHandle),
Dataclass {
name: String,
type_id: u64,
field_names: Vec<String>,
attrs: DictPairs,
frozen: bool,
},
Function {
name: String,
docstring: Option<String>,
},
Repr(String),
Cycle(usize, String),
}
impl fmt::Display for MontyObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::String(s) => f.write_str(s),
Self::Cycle(_, placeholder) => f.write_str(placeholder),
Self::Type(t) => write!(f, "<class '{t}'>"),
Self::Function { name, .. } => write!(f, "<function '{name}' external>"),
_ => self.repr_fmt(f),
}
}
}
impl MontyObject {
pub fn dict(dict: impl Into<DictPairs>) -> Self {
Self::Dict(dict.into())
}
#[must_use]
pub fn builtin_function_from_name(name: &str) -> Option<Self> {
name.parse::<BuiltinsFunctions>().ok().map(Self::BuiltinFunction)
}
pub fn host_size(&self) -> usize {
const BASE: usize = size_of::<MontyObject>();
const STR_OVERHEAD: usize = size_of::<String>();
let names_len = |names: &[String]| -> usize { names.iter().map(|s| STR_OVERHEAD + s.len()).sum() };
let payload = match self {
Self::String(s) | Self::Path(s) | Self::Repr(s) => s.len(),
Self::Cycle(_, placeholder) => placeholder.len(),
Self::Bytes(b) => b.len(),
Self::BigInt(bi) => usize::try_from(bi.bits().div_ceil(8)).unwrap_or(usize::MAX),
Self::Exception { arg, .. } => arg.as_ref().map_or(0, String::len),
Self::FileHandle(fh) => fh.path.len(),
Self::Function { name, docstring } => name.len() + docstring.as_ref().map_or(0, String::len),
Self::NamedTuple {
type_name, field_names, ..
} => type_name.len() + names_len(field_names),
Self::Dataclass { name, field_names, .. } => name.len() + names_len(field_names),
Self::Type(MontyType::Instance(name)) => name.len(),
_ => 0,
};
BASE + payload
}
#[must_use]
pub fn py_repr(&self) -> String {
let mut s = String::new();
self.repr_fmt(&mut s).expect("Unable to format repr display value");
s
}
fn repr_fmt(&self, f: &mut impl Write) -> fmt::Result {
match self {
Self::Ellipsis => f.write_str("Ellipsis"),
Self::None => f.write_str("None"),
Self::Bool(true) => f.write_str("True"),
Self::Bool(false) => f.write_str("False"),
Self::Int(v) => write!(f, "{v}"),
Self::BigInt(v) => write!(f, "{v}"),
Self::Float(v) => write!(f, "{}", FormatFloat(*v)),
Self::String(s) => string_repr_fmt(s, f),
Self::Bytes(b) => f.write_str(&bytes_repr(b)),
Self::List(l) => {
f.write_char('[')?;
let mut iter = l.iter();
if let Some(first) = iter.next() {
first.repr_fmt(f)?;
for item in iter {
f.write_str(", ")?;
item.repr_fmt(f)?;
}
}
f.write_char(']')
}
Self::Tuple(t) => {
f.write_char('(')?;
let mut iter = t.iter();
if let Some(first) = iter.next() {
first.repr_fmt(f)?;
for item in iter {
f.write_str(", ")?;
item.repr_fmt(f)?;
}
}
f.write_char(')')
}
Self::NamedTuple {
type_name,
field_names,
values,
} => {
f.write_str(type_name)?;
f.write_char('(')?;
let mut first = true;
for (name, value) in field_names.iter().zip(values) {
if !first {
f.write_str(", ")?;
}
first = false;
f.write_str(name)?;
f.write_char('=')?;
value.repr_fmt(f)?;
}
f.write_char(')')
}
Self::Dict(d) => {
f.write_char('{')?;
let mut iter = d.iter();
if let Some((k, v)) = iter.next() {
k.repr_fmt(f)?;
f.write_str(": ")?;
v.repr_fmt(f)?;
for (k, v) in iter {
f.write_str(", ")?;
k.repr_fmt(f)?;
f.write_str(": ")?;
v.repr_fmt(f)?;
}
}
f.write_char('}')
}
Self::Set(s) => {
if s.is_empty() {
f.write_str("set()")
} else {
f.write_char('{')?;
let mut iter = s.iter();
if let Some(first) = iter.next() {
first.repr_fmt(f)?;
for item in iter {
f.write_str(", ")?;
item.repr_fmt(f)?;
}
}
f.write_char('}')
}
}
Self::FrozenSet(fs) => {
f.write_str("frozenset(")?;
if !fs.is_empty() {
f.write_char('{')?;
let mut iter = fs.iter();
if let Some(first) = iter.next() {
first.repr_fmt(f)?;
for item in iter {
f.write_str(", ")?;
item.repr_fmt(f)?;
}
}
f.write_char('}')?;
}
f.write_char(')')
}
Self::Date(date) => write!(f, "datetime.date({}, {}, {})", date.year, date.month, date.day),
Self::DateTime(datetime) => {
write!(
f,
"datetime.datetime({}, {}, {}, {}, {}",
datetime.year, datetime.month, datetime.day, datetime.hour, datetime.minute
)?;
if datetime.second != 0 || datetime.microsecond != 0 {
write!(f, ", {}", datetime.second)?;
}
if datetime.microsecond != 0 {
write!(f, ", {}", datetime.microsecond)?;
}
if let Some(offset) = datetime.offset_seconds {
if offset == 0 && datetime.timezone_name.is_none() {
f.write_str(", tzinfo=datetime.timezone.utc")?;
} else {
let timedelta_repr = format_offset_timedelta_repr(offset);
write!(f, ", tzinfo=datetime.timezone({timedelta_repr}")?;
if let Some(name) = &datetime.timezone_name {
write!(f, ", {}", StringRepr(name))?;
}
f.write_char(')')?;
}
}
f.write_char(')')
}
Self::TimeDelta(delta) => {
if delta.days == 0 && delta.seconds == 0 && delta.microseconds == 0 {
return f.write_str("datetime.timedelta(0)");
}
f.write_str("datetime.timedelta(")?;
let mut first = true;
if delta.days != 0 {
write!(f, "days={}", delta.days)?;
first = false;
}
if delta.seconds != 0 {
if !first {
f.write_str(", ")?;
}
write!(f, "seconds={}", delta.seconds)?;
first = false;
}
if delta.microseconds != 0 {
if !first {
f.write_str(", ")?;
}
write!(f, "microseconds={}", delta.microseconds)?;
}
f.write_char(')')
}
Self::TimeZone(tz) => {
if tz.offset_seconds == 0 && tz.name.is_none() {
return f.write_str("datetime.timezone.utc");
}
let timedelta_repr = format_offset_timedelta_repr(tz.offset_seconds);
write!(f, "datetime.timezone({timedelta_repr}")?;
if let Some(name) = &tz.name {
write!(f, ", {}", StringRepr(name))?;
}
f.write_char(')')
}
Self::Exception { exc_type, arg } => {
let type_str: &'static str = exc_type.into();
write!(f, "{type_str}(")?;
if let Some(arg) = &arg {
string_repr_fmt(arg, f)?;
}
f.write_char(')')
}
Self::Dataclass {
name,
field_names,
attrs,
..
} => {
f.write_str(name)?;
f.write_char('(')?;
let mut first = true;
for field_name in field_names {
if !first {
f.write_str(", ")?;
}
first = false;
f.write_str(field_name)?;
f.write_char('=')?;
let key = Self::String(field_name.clone());
if let Some(value) = attrs.iter().find(|(k, _)| k == &key).map(|(_, v)| v) {
value.repr_fmt(f)?;
} else {
f.write_str("<?>")?;
}
}
f.write_char(')')
}
Self::Path(p) => write!(f, "PosixPath('{p}')"),
Self::FileHandle(handle) => write!(f, "{handle}"),
Self::Type(t) => write!(f, "<class '{t}'>"),
Self::BuiltinFunction(func) => write!(f, "<built-in function {func}>"),
Self::Function { name, .. } => write!(f, "<function '{name}' external>"),
Self::Repr(s) => write!(f, "Repr({})", StringRepr(s)),
Self::Cycle(_, placeholder) => f.write_str(placeholder),
}
}
#[must_use]
pub fn is_truthy(&self) -> bool {
match self {
Self::None => false,
Self::Ellipsis => true,
Self::Bool(b) => *b,
Self::Int(i) => *i != 0,
Self::BigInt(bi) => !bi.is_zero(),
Self::Float(f) => *f != 0.0,
Self::String(s) => !s.is_empty(),
Self::Bytes(b) => !b.is_empty(),
Self::List(l) => !l.is_empty(),
Self::Tuple(t) => !t.is_empty(),
Self::NamedTuple { values, .. } => !values.is_empty(),
Self::Dict(d) => !d.is_empty(),
Self::Set(s) => !s.is_empty(),
Self::FrozenSet(fs) => !fs.is_empty(),
Self::Date(_) => true,
Self::DateTime(_) => true,
Self::TimeDelta(delta) => delta.days != 0 || delta.seconds != 0 || delta.microseconds != 0,
Self::TimeZone(_) => true,
Self::Exception { .. } => true,
Self::Path(_) => true, Self::FileHandle { .. } => true, Self::Dataclass { .. } => true, Self::Type(_) | Self::BuiltinFunction(_) | Self::Function { .. } | Self::Repr(_) | Self::Cycle(_, _) => {
true
}
}
}
#[must_use]
pub fn type_name(&self) -> &'static str {
match self {
Self::None => "NoneType",
Self::Ellipsis => "ellipsis",
Self::Bool(_) => "bool",
Self::Int(_) | Self::BigInt(_) => "int",
Self::Float(_) => "float",
Self::String(_) => "str",
Self::Bytes(_) => "bytes",
Self::List(_) => "list",
Self::Tuple(_) => "tuple",
Self::NamedTuple { .. } => "namedtuple",
Self::Dict(_) => "dict",
Self::Set(_) => "set",
Self::FrozenSet(_) => "frozenset",
Self::Date(_) => "date",
Self::DateTime(_) => "datetime",
Self::TimeDelta(_) => "timedelta",
Self::TimeZone(_) => "timezone",
Self::Exception { .. } => "Exception",
Self::Path(_) => "PosixPath",
Self::FileHandle(handle) => handle.mode.type_name(),
Self::Dataclass { .. } => "dataclass",
Self::Type(_) => "type",
Self::BuiltinFunction(_) => "builtin_function_or_method",
Self::Function { .. } => "function",
Self::Repr(_) => "repr",
Self::Cycle(_, _) => "cycle",
}
}
}
impl Hash for MontyObject {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::Int(_) | Self::BigInt(_) => {
mem::discriminant(&Self::Int(0)).hash(state);
}
_ => mem::discriminant(self).hash(state),
}
match self {
Self::Ellipsis | Self::None => {}
Self::Bool(bool) => bool.hash(state),
Self::Int(i) => i.hash(state),
Self::BigInt(bi) => {
if let Ok(i) = i64::try_from(bi) {
i.hash(state);
} else {
bi.to_signed_bytes_le().hash(state);
}
}
Self::Float(f) => f.to_bits().hash(state),
Self::String(string) => string.hash(state),
Self::Bytes(bytes) => bytes.hash(state),
Self::Date(date) => date.hash(state),
Self::DateTime(datetime) => datetime.hash(state),
Self::TimeDelta(delta) => delta.hash(state),
Self::TimeZone(timezone) => timezone.hash(state),
Self::Path(path) => path.hash(state),
Self::FileHandle(MontyFileHandle { path, mode, position }) => {
path.hash(state);
mode.as_str().hash(state);
position.hash(state);
}
Self::Type(t) => t.name().hash(state),
Self::Cycle(_, _) => panic!("cycle values are not hashable"),
_ => panic!("{} python values are not hashable", self.type_name()),
}
}
}
impl PartialEq for MontyObject {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Ellipsis, Self::Ellipsis) => true,
(Self::None, Self::None) => true,
(Self::Bool(a), Self::Bool(b)) => a == b,
(Self::Int(a), Self::Int(b)) => a == b,
(Self::BigInt(a), Self::BigInt(b)) => a == b,
(Self::Int(a), Self::BigInt(b)) | (Self::BigInt(b), Self::Int(a)) => b.to_i64() == Some(*a),
(Self::Float(a), Self::Float(b)) => a.to_bits() == b.to_bits(),
(Self::String(a), Self::String(b)) => a == b,
(Self::Bytes(a), Self::Bytes(b)) => a == b,
(Self::List(a), Self::List(b)) => a == b,
(Self::Tuple(a), Self::Tuple(b)) => a == b,
(Self::Date(a), Self::Date(b)) => a == b,
(Self::DateTime(a), Self::DateTime(b)) => a == b,
(Self::TimeDelta(a), Self::TimeDelta(b)) => a == b,
(Self::TimeZone(a), Self::TimeZone(b)) => a == b,
(
Self::NamedTuple {
type_name: a_type,
field_names: a_fields,
values: a_values,
},
Self::NamedTuple {
type_name: b_type,
field_names: b_fields,
values: b_values,
},
) => a_type == b_type && a_fields == b_fields && a_values == b_values,
(Self::NamedTuple { values, .. }, Self::Tuple(t)) | (Self::Tuple(t), Self::NamedTuple { values, .. }) => {
values == t
}
(Self::Dict(a), Self::Dict(b)) => a == b,
(Self::Set(a), Self::Set(b)) => a == b,
(Self::FrozenSet(a), Self::FrozenSet(b)) => a == b,
(
Self::Exception {
exc_type: a_type,
arg: a_arg,
},
Self::Exception {
exc_type: b_type,
arg: b_arg,
},
) => a_type == b_type && a_arg == b_arg,
(
Self::Dataclass {
name: a_name,
type_id: a_type_id,
field_names: a_field_names,
attrs: a_attrs,
frozen: a_frozen,
},
Self::Dataclass {
name: b_name,
type_id: b_type_id,
field_names: b_field_names,
attrs: b_attrs,
frozen: b_frozen,
},
) => {
a_name == b_name
&& a_type_id == b_type_id
&& a_field_names == b_field_names
&& a_attrs == b_attrs
&& a_frozen == b_frozen
}
(Self::Path(a), Self::Path(b)) => a == b,
(
Self::FileHandle(MontyFileHandle {
path: a_path,
mode: a_mode,
position: a_pos,
}),
Self::FileHandle(MontyFileHandle {
path: b_path,
mode: b_mode,
position: b_pos,
}),
) => a_path == b_path && a_mode == b_mode && a_pos == b_pos,
(
Self::Function {
name: a_name,
docstring: a_doc,
},
Self::Function {
name: b_name,
docstring: b_doc,
},
) => a_name == b_name && a_doc == b_doc,
(Self::Repr(a), Self::Repr(b)) => a == b,
(Self::Cycle(a, _), Self::Cycle(b, _)) => a == b,
(Self::Type(a), Self::Type(b)) => a == b,
(Self::BuiltinFunction(a), Self::BuiltinFunction(b)) => a == b,
_ => false,
}
}
}
impl Eq for MontyObject {}
impl AsRef<Self> for MontyObject {
fn as_ref(&self) -> &Self {
self
}
}
#[derive(
Debug,
Clone,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
strum::EnumIter,
strum::EnumString,
strum::IntoStaticStr,
)]
#[strum(serialize_all = "lowercase")]
pub enum MontyType {
Ellipsis,
Type,
#[strum(serialize = "NoneType")]
NoneType,
Bool,
Int,
Float,
Range,
Slice,
Date,
#[strum(serialize = "datetime.datetime")]
DateTime,
TimeDelta,
TimeZone,
Str,
Bytes,
List,
#[strum(serialize = "list_iterator")]
ListIterator,
#[strum(serialize = "callable_iterator")]
CallableIterator,
Tuple,
NamedTuple,
Dict,
#[strum(serialize = "dict_keys")]
DictKeys,
#[strum(serialize = "dict_items")]
DictItems,
#[strum(serialize = "dict_values")]
DictValues,
Set,
FrozenSet,
Dataclass,
#[strum(disabled)]
Instance(String),
#[strum(disabled)]
Exception(ExcType),
Function,
#[strum(serialize = "builtin_function_or_method")]
BuiltinFunction,
Cell,
Iterator,
Coroutine,
Module,
#[strum(serialize = "_io.TextIOWrapper")]
TextIOWrapper,
#[strum(serialize = "_io.BufferedReader")]
BufferedReader,
#[strum(serialize = "_io.BufferedWriter")]
BufferedWriter,
#[strum(serialize = "_io.BufferedRandom")]
BufferedRandom,
#[strum(serialize = "typing._SpecialForm")]
SpecialForm,
#[strum(serialize = "PosixPath")]
Path,
Property,
#[strum(serialize = "re.Pattern")]
RePattern,
#[strum(serialize = "re.Match")]
ReMatch,
}
impl fmt::Display for MontyType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
impl MontyType {
#[must_use]
pub fn name(&self) -> &str {
match self {
Self::Instance(name) => name,
Self::Exception(exc_type) => (*exc_type).into(),
other => other.into(),
}
}
#[must_use]
pub fn from_type_name(name: &str) -> Option<Self> {
name.parse::<Self>()
.ok()
.or_else(|| name.parse::<ExcType>().ok().map(Self::Exception))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct MontyDate {
pub year: i32,
pub month: u8,
pub day: u8,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MontyDateTime {
pub year: i32,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: u8,
pub microsecond: u32,
pub offset_seconds: Option<i32>,
pub timezone_name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct MontyTimeDelta {
pub days: i32,
pub seconds: i32,
pub microseconds: i32,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MontyTimeZone {
pub offset_seconds: i32,
pub name: Option<String>,
}
impl PartialEq for MontyDateTime {
fn eq(&self, other: &Self) -> bool {
let self_aware = self.offset_seconds.is_some();
let other_aware = other.offset_seconds.is_some();
if self_aware != other_aware {
return false;
}
if self_aware {
return monty_datetime_utc_micros(self)
.zip(monty_datetime_utc_micros(other))
.is_some_and(|(lhs, rhs)| lhs == rhs)
|| monty_datetime_raw_eq(self, other);
}
monty_datetime_local_micros(self)
.zip(monty_datetime_local_micros(other))
.is_some_and(|(lhs, rhs)| lhs == rhs)
|| monty_datetime_raw_eq(self, other)
}
}
impl Eq for MontyDateTime {}
impl Hash for MontyDateTime {
fn hash<H: Hasher>(&self, state: &mut H) {
if self.offset_seconds.is_some()
&& let Some(utc_micros) = monty_datetime_utc_micros(self)
{
utc_micros.hash(state);
return;
}
if let Some(local_micros) = monty_datetime_local_micros(self) {
local_micros.hash(state);
return;
}
self.year.hash(state);
self.month.hash(state);
self.day.hash(state);
self.hour.hash(state);
self.minute.hash(state);
self.second.hash(state);
self.microsecond.hash(state);
self.offset_seconds.hash(state);
self.timezone_name.hash(state);
}
}
impl PartialEq for MontyTimeZone {
fn eq(&self, other: &Self) -> bool {
self.offset_seconds == other.offset_seconds
}
}
impl Eq for MontyTimeZone {}
impl Hash for MontyTimeZone {
fn hash<H: Hasher>(&self, state: &mut H) {
self.offset_seconds.hash(state);
}
}
#[derive(Debug)]
pub struct ConversionError {
pub expected: &'static str,
pub actual: &'static str,
}
impl ConversionError {
#[must_use]
pub fn new(expected: &'static str, actual: &'static str) -> Self {
Self { expected, actual }
}
}
impl fmt::Display for ConversionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "expected {}, got {}", self.expected, self.actual)
}
}
impl Error for ConversionError {}
#[derive(Debug, Clone)]
pub enum InvalidInputError {
InvalidType(Cow<'static, str>),
Resource(ResourceError),
}
impl InvalidInputError {
#[must_use]
pub fn invalid_type(msg: impl Into<Cow<'static, str>>) -> Self {
Self::InvalidType(msg.into())
}
}
impl fmt::Display for InvalidInputError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidType(msg) => write!(f, "{msg}"),
Self::Resource(e) => write!(f, "{e}"),
}
}
}
impl Error for InvalidInputError {}
impl From<ResourceError> for InvalidInputError {
fn from(err: ResourceError) -> Self {
Self::Resource(err)
}
}
impl TryFrom<&MontyObject> for i64 {
type Error = ConversionError;
fn try_from(value: &MontyObject) -> Result<Self, Self::Error> {
match value {
MontyObject::Int(i) => Ok(*i),
_ => Err(ConversionError::new("int", value.type_name())),
}
}
}
impl TryFrom<&MontyObject> for f64 {
type Error = ConversionError;
fn try_from(value: &MontyObject) -> Result<Self, Self::Error> {
match value {
MontyObject::Float(f) => Ok(*f),
MontyObject::Int(i) => Ok(*i as Self),
_ => Err(ConversionError::new("float", value.type_name())),
}
}
}
impl TryFrom<&MontyObject> for String {
type Error = ConversionError;
fn try_from(value: &MontyObject) -> Result<Self, Self::Error> {
if let MontyObject::String(s) = value {
Ok(s.clone())
} else {
Err(ConversionError::new("str", value.type_name()))
}
}
}
impl TryFrom<&MontyObject> for bool {
type Error = ConversionError;
fn try_from(value: &MontyObject) -> Result<Self, Self::Error> {
match value {
MontyObject::Bool(b) => Ok(*b),
_ => Err(ConversionError::new("bool", value.type_name())),
}
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct DictPairs(Vec<(MontyObject, MontyObject)>);
impl From<Vec<(MontyObject, MontyObject)>> for DictPairs {
fn from(pairs: Vec<(MontyObject, MontyObject)>) -> Self {
Self(pairs)
}
}
impl IntoIterator for DictPairs {
type Item = (MontyObject, MontyObject);
type IntoIter = IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a> IntoIterator for &'a DictPairs {
type Item = &'a (MontyObject, MontyObject);
type IntoIter = slice::Iter<'a, (MontyObject, MontyObject)>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl FromIterator<(MontyObject, MontyObject)> for DictPairs {
fn from_iter<T: IntoIterator<Item = (MontyObject, MontyObject)>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl DictPairs {
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn iter(&self) -> impl Iterator<Item = &(MontyObject, MontyObject)> {
self.0.iter()
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MontyFileHandle {
pub path: String,
pub mode: FileMode,
pub position: u64,
}
impl fmt::Display for MontyFileHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"<{} name={} mode={}>",
self.mode.file_type_name(),
StringRepr(&self.path),
StringRepr(self.mode.as_str())
)
}
}
fn monty_datetime_local_micros(datetime: &MontyDateTime) -> Option<i64> {
monty_datetime_naive(datetime).map(|naive| naive.and_utc().timestamp_micros())
}
fn monty_datetime_raw_eq(a: &MontyDateTime, b: &MontyDateTime) -> bool {
a.year == b.year
&& a.month == b.month
&& a.day == b.day
&& a.hour == b.hour
&& a.minute == b.minute
&& a.second == b.second
&& a.microsecond == b.microsecond
&& a.offset_seconds == b.offset_seconds
&& a.timezone_name == b.timezone_name
}
fn monty_datetime_utc_micros(datetime: &MontyDateTime) -> Option<i64> {
let offset_seconds = datetime.offset_seconds?;
let offset_delta = ChronoTimeDelta::try_seconds(i64::from(offset_seconds))?;
let utc = monty_datetime_naive(datetime)?.checked_sub_signed(offset_delta)?;
Some(utc.and_utc().timestamp_micros())
}
fn monty_datetime_naive(datetime: &MontyDateTime) -> Option<NaiveDateTime> {
let date = NaiveDate::from_ymd_opt(datetime.year, u32::from(datetime.month), u32::from(datetime.day))?;
let time = NaiveTime::from_hms_micro_opt(
u32::from(datetime.hour),
u32::from(datetime.minute),
u32::from(datetime.second),
datetime.microsecond,
)?;
Some(date.and_time(time))
}