Compare commits
2 Commits
push-puzom
...
push-xsnmp
| Author | SHA1 | Date | |
|---|---|---|---|
| 2453c34fb9 | |||
| 6c13e8cb8e |
23
.gitignore
vendored
23
.gitignore
vendored
@@ -1,5 +1,3 @@
|
||||
.ollama
|
||||
.ollamacode
|
||||
/bin/
|
||||
/pkg/
|
||||
/vendor/
|
||||
@@ -8,24 +6,14 @@
|
||||
**/*.o
|
||||
**/*.o.d
|
||||
**/*.a
|
||||
**/*.flac
|
||||
**/*.aif
|
||||
**/*.aiff
|
||||
**/*.wav
|
||||
**/*.opus
|
||||
**/*.mp4
|
||||
**/*.mp3
|
||||
**/*.ogg
|
||||
xxx
|
||||
/dcai/
|
||||
**/.pmomusic.yml
|
||||
**/.pmomusic_covers/**
|
||||
**/.pmomusic_audio/**
|
||||
/.pmomusic
|
||||
.DS_Store
|
||||
target
|
||||
/.pmomusic_covers
|
||||
/.pmomusic_audio/**
|
||||
**/.DS_Strore/**
|
||||
**/.DS_Strore
|
||||
/target/
|
||||
.pmomusic_covers
|
||||
C/src/soxr-0.1.3/Release/tests
|
||||
**/Release/
|
||||
**/Debug/
|
||||
@@ -34,6 +22,3 @@ xxx
|
||||
xx
|
||||
all.txt
|
||||
pmo_src.txt
|
||||
upmpdcli/
|
||||
/*.xml
|
||||
test_upnp
|
||||
@@ -1,28 +1,9 @@
|
||||
host:
|
||||
http_port: '8080'
|
||||
cover_cache:
|
||||
directory: ./.pmomusic_covers
|
||||
size: 2000
|
||||
audio_cache:
|
||||
directory: ./.pmomusic_audio
|
||||
size: 500
|
||||
logger:
|
||||
buffer_capacity: 200
|
||||
enable_console: true
|
||||
min_level: TRACE
|
||||
mediarenderer:
|
||||
mpd_renderer: null
|
||||
mediaserver:
|
||||
qobuz:
|
||||
udn: uuid:28963b75-4c5f-4da7-b10e-ffafd
|
||||
accounts:
|
||||
qobuz:
|
||||
username: eric@coissac.eu
|
||||
password: '*Misfcr73110$'
|
||||
devices:
|
||||
mediarenderer:
|
||||
pmo_mediarenderer:
|
||||
udn: 15a13316-daac-47f0-b64e-47e56f5e3b51
|
||||
fakerenderer:
|
||||
udn: d7eaad15-7d21-4411-926a-bc1eea0713db
|
||||
mediaserver:
|
||||
pmo_mediaserver:
|
||||
udn: 23df0bfa-cfef-4724-b731-00f66fadf176
|
||||
qobuz:
|
||||
udn: 28963b75-4c5f-4da7-b10e-ffafd
|
||||
host:
|
||||
http_port: '8080'
|
||||
|
||||
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@@ -3,13 +3,5 @@
|
||||
"git.enabled": false,
|
||||
"claude-code.environmentVariables": [
|
||||
|
||||
],
|
||||
// Exclusions via VS Code
|
||||
"files.exclude": {
|
||||
"target": true,
|
||||
"**/target": true,
|
||||
"node_modules": true
|
||||
},
|
||||
"rust-analyzer.procMacro.enable": true,
|
||||
"rust-analyzer.numThreads": 4
|
||||
]
|
||||
}
|
||||
3186
Cargo.lock
generated
3186
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
22
Cargo.toml
@@ -1,23 +1,3 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = [
|
||||
"PMOMusic",
|
||||
"pmoupnp",
|
||||
"pmomediarenderer",
|
||||
"pmomediaserver",
|
||||
"pmoconfig",
|
||||
"pmoutils",
|
||||
"pmodidl",
|
||||
"pmoserver",
|
||||
"pmoapp",
|
||||
"pmocache",
|
||||
"pmocovers",
|
||||
"pmoaudiocache",
|
||||
"pmoaudio",
|
||||
"pmoqobuz",
|
||||
"pmoparadise",
|
||||
"pmosource",
|
||||
"pmoplaylist",
|
||||
"pmoflac",
|
||||
"pmometadata",
|
||||
]
|
||||
members = ["PMOMusic", "pmoupnp","pmoconfig", "pmoutils", "pmodidl", "pmoserver", "pmoapp"]
|
||||
|
||||
24
Makefile
24
Makefile
@@ -1,8 +1,6 @@
|
||||
# Makefile pour projet Rust + Vue.js
|
||||
# Variables de configuration
|
||||
CARGO = cargo
|
||||
CARGO_NIGHTLY = rustup run nightly cargo
|
||||
FEATURES ?=
|
||||
NPM = npm
|
||||
WEBAPP_DIR = pmoapp/webapp
|
||||
DIST_DIR = $(WEBAPP_DIR)/dist
|
||||
@@ -16,9 +14,7 @@ YELLOW = \033[1;33m
|
||||
RED = \033[0;31m
|
||||
NC = \033[0m # No Color
|
||||
|
||||
.DEFAULT_GOAL := simd
|
||||
|
||||
.PHONY: all help build release debug test doc webapp clean install dev check fmt clippy watch simd scalar
|
||||
.PHONY: all help build release debug test doc webapp clean install dev check fmt clippy watch
|
||||
|
||||
# Cible par défaut
|
||||
all: build
|
||||
@@ -35,13 +31,13 @@ build: webapp release
|
||||
## release: Compile le binaire Rust en mode release
|
||||
release: webapp
|
||||
@echo "$(YELLOW)→ Compilation Rust (release)...$(NC)"
|
||||
$(CARGO) build --release $(FEATURES)
|
||||
$(CARGO) build --release
|
||||
@echo "$(GREEN)✓ Binaire disponible : $(RUST_TARGET)/$(BINARY_NAME)$(NC)"
|
||||
|
||||
## debug: Compile le binaire Rust en mode debug
|
||||
debug: webapp
|
||||
@echo "$(YELLOW)→ Compilation Rust (debug)...$(NC)"
|
||||
$(CARGO) build $(FEATURES)
|
||||
$(CARGO) build
|
||||
@echo "$(GREEN)✓ Binaire disponible : target/debug/$(BINARY_NAME)$(NC)"
|
||||
|
||||
## test: Exécute tous les tests Rust
|
||||
@@ -50,18 +46,6 @@ test:
|
||||
$(CARGO) test --all
|
||||
@echo "$(GREEN)✓ Tests terminés$(NC)"
|
||||
|
||||
## simd: Compile l'application en mode SIMD (nightly requis)
|
||||
simd:
|
||||
@echo "$(YELLOW)→ Build SIMD (nightly)...$(NC)"
|
||||
$(MAKE) release CARGO="$(CARGO_NIGHTLY)" FEATURES="--features simd"
|
||||
@echo "$(GREEN)✓ Build SIMD terminé$(NC)"
|
||||
|
||||
## scalar: Compile l'application en mode scalaire
|
||||
scalar:
|
||||
@echo "$(YELLOW)→ Build scalaire...$(NC)"
|
||||
$(MAKE) release FEATURES=""
|
||||
@echo "$(GREEN)✓ Build scalaire terminé$(NC)"
|
||||
|
||||
## test-doc: Teste les exemples dans la documentation
|
||||
test-doc:
|
||||
@echo "$(YELLOW)→ Test des exemples de documentation...$(NC)"
|
||||
@@ -203,4 +187,4 @@ coverage:
|
||||
@echo "$(YELLOW)→ Génération du rapport de couverture...$(NC)"
|
||||
$(CARGO) tarpaulin --out Html --output-dir target/coverage
|
||||
@echo "$(GREEN)✓ Rapport disponible dans target/coverage/index.html$(NC)"
|
||||
|
||||
|
||||
@@ -6,19 +6,12 @@ edition = "2024"
|
||||
[dependencies]
|
||||
pmoconfig = { path = "../pmoconfig" }
|
||||
pmoupnp = { path = "../pmoupnp"}
|
||||
pmomediarenderer = { path = "../pmomediarenderer" }
|
||||
pmomediaserver = { path = "../pmomediaserver", features = ["qobuz", "paradise", "paradise-api", "api"] }
|
||||
pmosource = { path = "../pmosource", features = ["server"] }
|
||||
pmoserver = { path = "../pmoserver" }
|
||||
pmocovers = { path = "../pmocovers", features = ["pmoserver"] }
|
||||
pmoaudiocache = { path = "../pmoaudiocache", features = ["pmoserver"]}
|
||||
pmoaudio-ext = { path = "../pmoaudio-ext", features = ["all"] }
|
||||
pmoapp = { path = "../pmoapp", features = ["pmoserver"] }
|
||||
pmoapp = { path = "../pmoapp" }
|
||||
|
||||
|
||||
tokio = { version = "1.35", features = ["rt-multi-thread", "macros", "sync", "time","signal"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.20"
|
||||
axum = "0.8.4"
|
||||
serde_json = "1.0.145"
|
||||
utoipa = "5.4"
|
||||
console-subscriber = "0.4.1"
|
||||
|
||||
@@ -1,102 +1,58 @@
|
||||
use pmoapp::{WebAppExt, Webapp};
|
||||
use pmomediarenderer::MEDIA_RENDERER;
|
||||
use pmomediaserver::{MEDIA_SERVER, sources::SourcesExt};
|
||||
use pmoserver::Server;
|
||||
use pmosource::MusicSourceExt;
|
||||
use pmoupnp::UpnpServerExt;
|
||||
use pmoupnp::{mediarenderer::avtransport::AVTTRANSPORT, UpnpObject};
|
||||
use pmoserver::{
|
||||
logs::{log_dump, log_sse, LogState, SseLayer},
|
||||
ServerBuilder
|
||||
};
|
||||
use pmoapp::Webapp;
|
||||
use tracing_subscriber::Registry;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing::info;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// ========== PHASE 1 : Infrastructure UPnP ==========
|
||||
// #[cfg(tokio_unstable)]
|
||||
// console_subscriber::init();
|
||||
async fn main() {
|
||||
// Charger la config
|
||||
|
||||
let mut server = ServerBuilder::new_configured().build();
|
||||
|
||||
// Ajouter des routes
|
||||
server
|
||||
.add_route("/hello", || async {
|
||||
serde_json::json!({"message": "Hello World"})
|
||||
})
|
||||
.await;
|
||||
|
||||
let server = Server::create_upnp_server().await?; // Routes personnalisées de l'application
|
||||
server
|
||||
.write()
|
||||
.await
|
||||
.add_route("/info", || async {
|
||||
serde_json::json!({"version": "1.0.0"})
|
||||
})
|
||||
.await;
|
||||
|
||||
// Initialiser le système de gestion des sources musicales avec API REST
|
||||
info!("📡 Initializing music sources management system...");
|
||||
server.add_spa::<Webapp>("/app").await;
|
||||
|
||||
// Gère la sortie des logs et sur le serveur SSE pour l'interface web et sur la console
|
||||
let log_state = LogState::new(1000);
|
||||
let subscriber = Registry::default()
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_target(true)
|
||||
.with_level(true)
|
||||
.with_ansi(true), // Couleurs dans le terminal
|
||||
)
|
||||
.with(SseLayer::new(log_state.clone()));
|
||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
|
||||
server
|
||||
.write()
|
||||
.await
|
||||
.init_music_sources()
|
||||
.await
|
||||
.expect("Failed to initialize music sources API");
|
||||
|
||||
// ========== PHASE 2 : Configuration métier ==========
|
||||
|
||||
// Enregistrer les sources musicales
|
||||
info!("🎵 Registering music sources...");
|
||||
|
||||
// // Enregistrer Qobuz
|
||||
// if let Err(e) = server.register_qobuz().await {
|
||||
// tracing::warn!("⚠️ Failed to register Qobuz: {}", e);
|
||||
// }
|
||||
|
||||
// Enregistrer Radio Paradise (inclut l'initialisation de l'API)
|
||||
if let Err(e) = server.write().await.register_paradise().await {
|
||||
tracing::warn!("⚠️ Failed to register Radio Paradise: {}", e);
|
||||
}
|
||||
|
||||
// Lister toutes les sources enregistrées
|
||||
let sources = server.read().await.list_music_sources().await;
|
||||
info!("✅ {} music source(s) registered", sources.len());
|
||||
for source in sources {
|
||||
info!(" - {} ({})", source.name(), source.id());
|
||||
}
|
||||
|
||||
// Enregistrer les devices UPnP (HTTP + SSDP automatique)
|
||||
info!("📡 Registering UPnP devices...");
|
||||
|
||||
let renderer_instance = server
|
||||
.write()
|
||||
.await
|
||||
.register_device(MEDIA_RENDERER.clone())
|
||||
.await
|
||||
.expect("Failed to register MediaRenderer");
|
||||
|
||||
info!(
|
||||
"✅ MediaRenderer ready at {}{}",
|
||||
renderer_instance.base_url(),
|
||||
renderer_instance.description_route()
|
||||
);
|
||||
|
||||
let server_instance = server
|
||||
.write()
|
||||
.await
|
||||
.register_device(MEDIA_SERVER.clone())
|
||||
.await
|
||||
.expect("Failed to register MediaServer");
|
||||
|
||||
info!(
|
||||
"✅ MediaServer ready at {}{}",
|
||||
server_instance.base_url(),
|
||||
server_instance.description_route()
|
||||
);
|
||||
|
||||
// Ajouter la webapp via le trait WebAppExt
|
||||
info!("📡 Registering Web application...");
|
||||
.add_handler_with_state("/log-sse", log_sse, log_state.clone())
|
||||
.await;
|
||||
server
|
||||
.write()
|
||||
.await
|
||||
.add_webapp_with_redirect::<Webapp>("/app")
|
||||
.add_handler_with_state("/log-dump", log_dump, log_state.clone())
|
||||
.await;
|
||||
|
||||
// ========== PHASE 3 : Démarrage du serveur ==========
|
||||
server.add_redirect("/", "/app").await;
|
||||
|
||||
info!("🌐 Starting HTTP server...");
|
||||
server.write().await.start().await;
|
||||
info!("{}",AVTTRANSPORT.to_markdown());
|
||||
info!("{}",AVTTRANSPORT.scpd_xml());
|
||||
|
||||
info!("✅ PMOMusic is ready!");
|
||||
info!("Press Ctrl+C to stop...");
|
||||
server.write().await.wait().await;
|
||||
|
||||
Ok(())
|
||||
server.start().await;
|
||||
server.wait().await;
|
||||
}
|
||||
|
||||
@@ -98,10 +98,3 @@ jj rebase --continue
|
||||
|
||||
pour résoudre les conflits
|
||||
|
||||
## Installer rust sur mac
|
||||
|
||||
```bash
|
||||
brew install rustup-init
|
||||
rustup-init
|
||||
rustup default stable
|
||||
```
|
||||
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
HTTP/1.1 200 OK
|
||||
content-type: text/xml; charset="utf-8"
|
||||
content-length: 1593
|
||||
date: Mon, 20 Oct 2025 17:44:48 GMT
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
use crate::{
|
||||
nodes::{AudioError, MultiSubscriberNode},
|
||||
AudioSegment,
|
||||
};
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
|
||||
/// Subscriber avec son propre offset dans le buffer
|
||||
struct BufferSubscriber {
|
||||
tx: mpsc::Sender<Arc<AudioSegment>>,
|
||||
offset: usize, // Position dans le buffer circulaire
|
||||
}
|
||||
|
||||
/// BufferNode avec buffer circulaire pour support multiroom
|
||||
///
|
||||
/// Ce node maintient un buffer circulaire de chunks et permet à plusieurs
|
||||
/// abonnés de lire avec des offsets différents, ce qui est idéal pour des
|
||||
/// configurations multiroom où différentes pièces peuvent avoir un léger
|
||||
/// délai de synchronisation.
|
||||
///
|
||||
/// # Fonctionnement
|
||||
///
|
||||
/// - Le buffer est implémenté avec un `VecDeque` de taille fixe
|
||||
/// - Chaque abonné peut avoir un offset indépendant (en nombre de chunks)
|
||||
/// - Utilise `try_send` pour éviter de bloquer si un abonné est saturé
|
||||
///
|
||||
/// # Exemples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use pmoaudio::{BufferNode, SinkNode};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (buffer, buffer_tx) = BufferNode::new(50, 10);
|
||||
///
|
||||
/// let (sink1, sink1_tx) = SinkNode::new("Room 1".to_string(), 10);
|
||||
/// let (sink2, sink2_tx) = SinkNode::new("Room 2".to_string(), 10);
|
||||
///
|
||||
/// // Room 1 sans délai
|
||||
/// buffer.add_subscriber_with_offset(sink1_tx, 0).await;
|
||||
///
|
||||
/// // Room 2 avec 5 chunks de retard
|
||||
/// buffer.add_subscriber_with_offset(sink2_tx, 5).await;
|
||||
///
|
||||
/// tokio::spawn(async move { buffer.run().await.unwrap() });
|
||||
/// // ... spawn sinks et source
|
||||
/// }
|
||||
/// ```
|
||||
pub struct BufferNode {
|
||||
buffer: Arc<RwLock<VecDeque<Arc<AudioSegment>>>>,
|
||||
subscribers: Arc<RwLock<Vec<BufferSubscriber>>>,
|
||||
buffer_size: usize,
|
||||
rx: mpsc::Receiver<Arc<AudioSegment>>,
|
||||
next_subscribers: MultiSubscriberNode, // Pour passer au node suivant
|
||||
}
|
||||
|
||||
impl BufferNode {
|
||||
/// Crée un nouveau BufferNode
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `buffer_size` - Taille maximale du buffer circulaire
|
||||
/// * `channel_size` - Taille du channel bounded pour backpressure
|
||||
pub fn new(buffer_size: usize, channel_size: usize) -> (Self, mpsc::Sender<Arc<AudioSegment>>) {
|
||||
let (tx, rx) = mpsc::channel(channel_size);
|
||||
|
||||
let node = Self {
|
||||
buffer: Arc::new(RwLock::new(VecDeque::with_capacity(buffer_size))),
|
||||
subscribers: Arc::new(RwLock::new(Vec::new())),
|
||||
buffer_size,
|
||||
rx,
|
||||
next_subscribers: MultiSubscriberNode::new(),
|
||||
};
|
||||
|
||||
(node, tx)
|
||||
}
|
||||
|
||||
/// Ajoute un abonné avec un offset spécifique (pour multiroom)
|
||||
pub async fn add_subscriber_with_offset(
|
||||
&self,
|
||||
tx: mpsc::Sender<Arc<AudioSegment>>,
|
||||
offset: usize,
|
||||
) {
|
||||
let mut subs = self.subscribers.write().await;
|
||||
subs.push(BufferSubscriber { tx, offset });
|
||||
}
|
||||
|
||||
/// Ajoute un abonné sans offset (commence au chunk courant)
|
||||
pub async fn add_subscriber(&self, tx: mpsc::Sender<Arc<AudioSegment>>) {
|
||||
self.add_subscriber_with_offset(tx, 0).await;
|
||||
}
|
||||
|
||||
/// Ajoute un abonné pour le node suivant (sans buffer)
|
||||
pub fn add_next_subscriber(&mut self, tx: mpsc::Sender<Arc<AudioSegment>>) {
|
||||
self.next_subscribers.add_subscriber(tx);
|
||||
}
|
||||
|
||||
/// Démarre la boucle de traitement du BufferNode
|
||||
pub async fn run(mut self) -> Result<(), AudioError> {
|
||||
let mut chunk_index = 0usize;
|
||||
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
// Ajouter au buffer circulaire
|
||||
{
|
||||
let mut buffer = self.buffer.write().await;
|
||||
if buffer.len() >= self.buffer_size {
|
||||
buffer.pop_front();
|
||||
}
|
||||
buffer.push_back(chunk.clone());
|
||||
}
|
||||
|
||||
// Envoyer aux abonnés avec offset
|
||||
{
|
||||
let buffer = self.buffer.read().await;
|
||||
let mut subs = self.subscribers.write().await;
|
||||
|
||||
for sub in subs.iter_mut() {
|
||||
// Calculer l'index dans le buffer en fonction de l'offset
|
||||
let target_index = if chunk_index >= sub.offset {
|
||||
chunk_index - sub.offset
|
||||
} else {
|
||||
continue; // Pas encore assez de données
|
||||
};
|
||||
|
||||
// Vérifier si le chunk est disponible dans le buffer
|
||||
let buffer_age = chunk_index - target_index;
|
||||
if buffer_age < buffer.len() {
|
||||
let chunk_to_send = &buffer[buffer.len() - buffer_age - 1];
|
||||
// try_send non-bloquant pour éviter de bloquer la source
|
||||
let _ = sub.tx.try_send(chunk_to_send.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Push vers les nodes suivants sans buffer
|
||||
self.next_subscribers.try_push(chunk).await?;
|
||||
|
||||
chunk_index += 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Version avec push synchrone au lieu de try_push
|
||||
pub async fn run_blocking(mut self) -> Result<(), AudioError> {
|
||||
let mut chunk_index = 0usize;
|
||||
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
// Ajouter au buffer circulaire
|
||||
{
|
||||
let mut buffer = self.buffer.write().await;
|
||||
if buffer.len() >= self.buffer_size {
|
||||
buffer.pop_front();
|
||||
}
|
||||
buffer.push_back(chunk.clone());
|
||||
}
|
||||
|
||||
// Envoyer aux abonnés avec offset
|
||||
{
|
||||
let buffer = self.buffer.read().await;
|
||||
let subs = self.subscribers.read().await;
|
||||
|
||||
for sub in subs.iter() {
|
||||
let target_index = if chunk_index >= sub.offset {
|
||||
chunk_index - sub.offset
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let buffer_age = chunk_index - target_index;
|
||||
if buffer_age < buffer.len() {
|
||||
let chunk_to_send = &buffer[buffer.len() - buffer_age - 1];
|
||||
let _ = sub.tx.send(chunk_to_send.clone()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Push vers les nodes suivants
|
||||
for _ in 0..self.next_subscribers.subscribers.len() {
|
||||
self.next_subscribers.push(chunk.clone()).await?;
|
||||
}
|
||||
|
||||
chunk_index += 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::BitDepth;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_buffer_node_basic() {
|
||||
let (mut node, tx) = BufferNode::new(10, 5);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(5);
|
||||
|
||||
node.add_next_subscriber(out_tx);
|
||||
|
||||
// Spawn le node
|
||||
tokio::spawn(async move {
|
||||
node.run().await.unwrap();
|
||||
});
|
||||
|
||||
// Envoyer des chunks
|
||||
for i in 0..3 {
|
||||
let chunk = AudioSegment::AudioChunk(AudioChunk::new(i, vec![[0i32; 2]; 100], 48000, BitDepth::B24));
|
||||
tx.send(chunk).await.unwrap();
|
||||
}
|
||||
|
||||
// Recevoir les chunks
|
||||
for i in 0..3 {
|
||||
let chunk = out_rx.recv().await.unwrap();
|
||||
assert_eq!(chunk.order(), i);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_buffer_node_with_offset() {
|
||||
let (node, tx) = BufferNode::new(10, 10);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
// Ajouter un abonné avec offset de 2 chunks
|
||||
node.add_subscriber_with_offset(out_tx, 2).await;
|
||||
|
||||
// Spawn le node
|
||||
tokio::spawn(async move {
|
||||
node.run().await.unwrap();
|
||||
});
|
||||
|
||||
// Envoyer 5 chunks
|
||||
for i in 0..5 {
|
||||
let chunk = AudioSegment::new(i, vec![[0i32; 2]; 100], 48000, BitDepth::B24);
|
||||
tx.send(chunk).await.unwrap();
|
||||
}
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||
|
||||
// L'abonné devrait recevoir les chunks 0, 1, 2 (avec 2 chunks de retard)
|
||||
let chunk = out_rx.try_recv().unwrap();
|
||||
assert_eq!(chunk.order(), 0);
|
||||
}
|
||||
}
|
||||
@@ -1,290 +0,0 @@
|
||||
//! ChromecastSink - Diffuse le flux audio vers un périphérique Chromecast
|
||||
//!
|
||||
//! Ce module fournit un sink qui envoie le flux audio à un Chromecast.
|
||||
//! Note: Cette implémentation est une version mock/skeleton. Une vraie implémentation
|
||||
//! nécessiterait une bibliothèque comme `rust-cast` ou similaire.
|
||||
|
||||
use crate::{nodes::AudioError, AudioChunk};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// Configuration pour le ChromecastSink
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChromecastConfig {
|
||||
/// Nom ou adresse IP du Chromecast
|
||||
pub device_address: String,
|
||||
|
||||
/// Nom amical du device
|
||||
pub device_name: String,
|
||||
|
||||
/// Port de communication (défaut: 8009)
|
||||
pub port: u16,
|
||||
|
||||
/// Taille du buffer de streaming
|
||||
pub buffer_size: usize,
|
||||
|
||||
/// Format d'encodage pour le streaming
|
||||
pub encoding: StreamEncoding,
|
||||
}
|
||||
|
||||
impl Default for ChromecastConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
device_address: "192.168.1.100".to_string(),
|
||||
device_name: "Living Room".to_string(),
|
||||
port: 8009,
|
||||
buffer_size: 50,
|
||||
encoding: StreamEncoding::Mp3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats d'encodage supportés pour le streaming
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum StreamEncoding {
|
||||
/// MP3 (compatible avec la plupart des Chromecasts)
|
||||
Mp3,
|
||||
/// AAC
|
||||
Aac,
|
||||
/// Opus
|
||||
Opus,
|
||||
/// PCM non compressé (haute qualité, bande passante élevée)
|
||||
Pcm,
|
||||
}
|
||||
|
||||
/// ChromecastSink - Diffuse vers un périphérique Chromecast
|
||||
///
|
||||
/// Ce sink encode le flux audio et le streame vers un Chromecast.
|
||||
/// La connexion est établie lors de l'initialisation et maintenue pendant toute la durée.
|
||||
///
|
||||
/// # Implémentation actuelle
|
||||
///
|
||||
/// Cette version est un mock qui simule l'envoi au Chromecast.
|
||||
/// Pour une vraie implémentation, il faudrait:
|
||||
/// - Utiliser une bibliothèque comme `rust-cast`
|
||||
/// - Établir une connexion TLS avec le device
|
||||
/// - Lancer une application de récepteur sur le Chromecast
|
||||
/// - Encoder l'audio dans le format approprié
|
||||
/// - Streamer via HTTP ou WebSocket
|
||||
///
|
||||
/// # Exemples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use pmoaudio::{ChromecastSink, ChromecastConfig};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let config = ChromecastConfig {
|
||||
/// device_address: "192.168.1.100".to_string(),
|
||||
/// device_name: "Living Room".to_string(),
|
||||
/// ..Default::default()
|
||||
/// };
|
||||
///
|
||||
/// let (sink, sink_tx) = ChromecastSink::new("chromecast1".to_string(), config, 10);
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// sink.run().await.unwrap()
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
pub struct ChromecastSink {
|
||||
/// Identifiant du sink
|
||||
node_id: String,
|
||||
|
||||
/// Channel pour recevoir les chunks audio
|
||||
rx: mpsc::Receiver<Arc<AudioChunk>>,
|
||||
|
||||
/// Configuration
|
||||
config: ChromecastConfig,
|
||||
|
||||
/// État de la connexion (mock)
|
||||
connected: bool,
|
||||
}
|
||||
|
||||
impl ChromecastSink {
|
||||
/// Crée un nouveau ChromecastSink
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `node_id` - Identifiant unique du sink
|
||||
/// * `config` - Configuration du Chromecast
|
||||
/// * `channel_size` - Taille du buffer du channel
|
||||
pub fn new(
|
||||
node_id: String,
|
||||
config: ChromecastConfig,
|
||||
channel_size: usize,
|
||||
) -> (Self, mpsc::Sender<Arc<AudioChunk>>) {
|
||||
let (tx, rx) = mpsc::channel(channel_size);
|
||||
|
||||
let sink = Self {
|
||||
node_id,
|
||||
rx,
|
||||
config,
|
||||
connected: false,
|
||||
};
|
||||
|
||||
(sink, tx)
|
||||
}
|
||||
|
||||
/// Établit la connexion avec le Chromecast (mock)
|
||||
async fn connect(&mut self) -> Result<(), AudioError> {
|
||||
println!(
|
||||
"[{}] Connecting to Chromecast '{}' at {}:{}...",
|
||||
self.node_id, self.config.device_name, self.config.device_address, self.config.port
|
||||
);
|
||||
|
||||
// Simuler une connexion
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
|
||||
self.connected = true;
|
||||
|
||||
println!(
|
||||
"[{}] Connected to Chromecast '{}' successfully",
|
||||
self.node_id, self.config.device_name
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Envoie un chunk au Chromecast (mock)
|
||||
async fn send_chunk(&self, _chunk: &AudioChunk) -> Result<(), AudioError> {
|
||||
if !self.connected {
|
||||
return Err(AudioError::ProcessingError(
|
||||
"Not connected to Chromecast".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Dans une vraie implémentation:
|
||||
// 1. Appliquer le gain
|
||||
// 2. Encoder dans le format approprié (MP3, AAC, etc.)
|
||||
// 3. Envoyer via le protocole Chromecast
|
||||
|
||||
// Pour l'instant, simplement simuler un délai d'envoi
|
||||
tokio::time::sleep(tokio::time::Duration::from_micros(50)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Déconnecte proprement du Chromecast (mock)
|
||||
async fn disconnect(&mut self) -> Result<(), AudioError> {
|
||||
if self.connected {
|
||||
println!(
|
||||
"[{}] Disconnecting from Chromecast '{}'...",
|
||||
self.node_id, self.config.device_name
|
||||
);
|
||||
|
||||
// Simuler la déconnexion
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
|
||||
self.connected = false;
|
||||
|
||||
println!("[{}] Disconnected successfully", self.node_id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Démarre la boucle de traitement du ChromecastSink
|
||||
pub async fn run(mut self) -> Result<ChromecastStats, AudioError> {
|
||||
// Établir la connexion
|
||||
self.connect().await?;
|
||||
|
||||
let mut stats = ChromecastStats::new(self.node_id.clone(), self.config.device_name.clone());
|
||||
|
||||
// Boucle principale
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
// Appliquer le gain si nécessaire
|
||||
let chunk_to_send = if chunk.gain_db().abs() > f64::EPSILON {
|
||||
Arc::clone(&chunk).apply_gain()
|
||||
} else {
|
||||
Arc::clone(&chunk)
|
||||
};
|
||||
|
||||
// Envoyer au Chromecast
|
||||
self.send_chunk(&chunk_to_send).await?;
|
||||
|
||||
stats.record_chunk(&chunk_to_send);
|
||||
}
|
||||
|
||||
// Déconnexion propre
|
||||
self.disconnect().await?;
|
||||
|
||||
stats.finalize();
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
/// Statistiques du ChromecastSink
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChromecastStats {
|
||||
pub node_id: String,
|
||||
pub device_name: String,
|
||||
pub chunks_sent: u64,
|
||||
pub total_samples: u64,
|
||||
pub total_duration_sec: f64,
|
||||
}
|
||||
|
||||
impl ChromecastStats {
|
||||
pub fn new(node_id: String, device_name: String) -> Self {
|
||||
Self {
|
||||
node_id,
|
||||
device_name,
|
||||
chunks_sent: 0,
|
||||
total_samples: 0,
|
||||
total_duration_sec: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_chunk(&mut self, chunk: &AudioChunk) {
|
||||
self.chunks_sent += 1;
|
||||
self.total_samples += chunk.len() as u64;
|
||||
self.total_duration_sec += chunk.len() as f64 / chunk.sample_rate() as f64;
|
||||
}
|
||||
|
||||
pub fn finalize(&mut self) {
|
||||
// Calculs finaux si nécessaire
|
||||
}
|
||||
|
||||
pub fn display(&self) {
|
||||
println!("\n=== Chromecast Statistics: {} ===", self.node_id);
|
||||
println!("Device: {}", self.device_name);
|
||||
println!("Chunks sent: {}", self.chunks_sent);
|
||||
println!("Total samples: {}", self.total_samples);
|
||||
println!("Total duration: {:.3} sec", self.total_duration_sec);
|
||||
println!("==================================\n");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::i32;
|
||||
|
||||
use super::*;
|
||||
use crate::BitDepth;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_chromecast_sink_basic() {
|
||||
let config = ChromecastConfig {
|
||||
device_address: "127.0.0.1".to_string(),
|
||||
device_name: "Test Device".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (sink, tx) = ChromecastSink::new("test".to_string(), config, 10);
|
||||
|
||||
let handle = tokio::spawn(async move { sink.run().await });
|
||||
|
||||
// Envoyer quelques chunks
|
||||
for i in 0..5 {
|
||||
let stereo = vec![[i32::MAX / 2; 2]; 1000];
|
||||
let chunk = AudioChunk::new(i, stereo, 48000, BitDepth::B24);
|
||||
tx.send(chunk).await.unwrap();
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
|
||||
let stats = handle.await.unwrap().unwrap();
|
||||
assert_eq!(stats.chunks_sent, 5);
|
||||
assert_eq!(stats.device_name, "Test Device");
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
use crate::{
|
||||
nodes::{AudioError, MultiSubscriberNode},
|
||||
AudioChunk,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// DecoderNode - Décode des chunks audio
|
||||
///
|
||||
/// Version mock qui passe simplement les chunks (ou simule un décodage simple)
|
||||
pub struct DecoderNode {
|
||||
rx: mpsc::Receiver<Arc<AudioChunk>>,
|
||||
subscribers: MultiSubscriberNode,
|
||||
}
|
||||
|
||||
impl DecoderNode {
|
||||
pub fn new(channel_size: usize) -> (Self, mpsc::Sender<Arc<AudioChunk>>) {
|
||||
let (tx, rx) = mpsc::channel(channel_size);
|
||||
|
||||
let node = Self {
|
||||
rx,
|
||||
subscribers: MultiSubscriberNode::new(),
|
||||
};
|
||||
|
||||
(node, tx)
|
||||
}
|
||||
|
||||
pub fn add_subscriber(&mut self, tx: mpsc::Sender<Arc<AudioChunk>>) {
|
||||
self.subscribers.add_subscriber(tx);
|
||||
}
|
||||
|
||||
/// Mode passthrough - passe les chunks sans modification
|
||||
pub async fn run_passthrough(mut self) -> Result<(), AudioError> {
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
self.subscribers.push(chunk).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mode mock décodage - simule un changement de sample rate
|
||||
pub async fn run_with_resampling(mut self, target_sample_rate: u32) -> Result<(), AudioError> {
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
if chunk.sample_rate() == target_sample_rate {
|
||||
// Pas besoin de resampling
|
||||
self.subscribers.push(chunk).await?;
|
||||
} else {
|
||||
// Simuler un resampling (mock simple)
|
||||
let ratio = target_sample_rate as f64 / chunk.sample_rate() as f64;
|
||||
let new_len = (chunk.len() as f64 * ratio) as usize;
|
||||
|
||||
let pairs = chunk.to_pairs_f32();
|
||||
let mut resampled = Vec::with_capacity(new_len);
|
||||
|
||||
// Resampling linéaire simple (mock)
|
||||
for i in 0..new_len {
|
||||
let src_pos = i as f64 / ratio;
|
||||
let src_idx = src_pos as usize;
|
||||
|
||||
if src_idx + 1 < pairs.len() {
|
||||
let frac = src_pos - src_idx as f64;
|
||||
let alpha = (1.0 - frac) as f32;
|
||||
let beta = frac as f32;
|
||||
let left_sample = pairs[src_idx][0] * alpha + pairs[src_idx + 1][0] * beta;
|
||||
let right_sample = pairs[src_idx][1] * alpha + pairs[src_idx + 1][1] * beta;
|
||||
|
||||
resampled.push([left_sample, right_sample]);
|
||||
} else if src_idx < pairs.len() {
|
||||
resampled.push(pairs[src_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_chunk = AudioChunk::from_pairs_f32(
|
||||
chunk.order(),
|
||||
resampled,
|
||||
target_sample_rate,
|
||||
chunk.bit_depth(),
|
||||
);
|
||||
if chunk.gain_db().abs() > f64::EPSILON {
|
||||
new_chunk = new_chunk.set_gain_db(chunk.gain_db());
|
||||
}
|
||||
self.subscribers.push(new_chunk).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::BitDepth;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_decoder_passthrough() {
|
||||
let (mut node, tx) = DecoderNode::new(10);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx);
|
||||
|
||||
tokio::spawn(async move {
|
||||
node.run_passthrough().await.unwrap();
|
||||
});
|
||||
|
||||
// Envoyer un chunk
|
||||
let chunk = AudioChunk::from_channels_f32(
|
||||
0,
|
||||
vec![1.0, 2.0, 3.0],
|
||||
vec![4.0, 5.0, 6.0],
|
||||
48000,
|
||||
BitDepth::B24,
|
||||
);
|
||||
tx.send(chunk.clone()).await.unwrap();
|
||||
|
||||
// Recevoir le chunk
|
||||
let received = out_rx.recv().await.unwrap();
|
||||
assert!(Arc::ptr_eq(&chunk, &received));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_decoder_resampling() {
|
||||
let (mut node, tx) = DecoderNode::new(10);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx);
|
||||
|
||||
tokio::spawn(async move {
|
||||
node.run_with_resampling(96000).await.unwrap();
|
||||
});
|
||||
|
||||
// Envoyer un chunk à 48000 Hz
|
||||
let chunk =
|
||||
AudioChunk::from_channels_f32(0, vec![1.0; 100], vec![1.0; 100], 48000, BitDepth::B24);
|
||||
tx.send(chunk).await.unwrap();
|
||||
|
||||
// Recevoir le chunk resampleé
|
||||
let received = out_rx.recv().await.unwrap();
|
||||
assert_eq!(received.sample_rate(), 96000);
|
||||
// Le chunk devrait être environ 2x plus grand
|
||||
assert!(received.len() > 150 && received.len() < 250);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_decoder_no_resampling_needed() {
|
||||
let (mut node, tx) = DecoderNode::new(10);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx);
|
||||
|
||||
tokio::spawn(async move {
|
||||
node.run_with_resampling(48000).await.unwrap();
|
||||
});
|
||||
|
||||
// Envoyer un chunk déjà au bon sample rate
|
||||
let chunk =
|
||||
AudioChunk::from_channels_f32(0, vec![1.0; 100], vec![1.0; 100], 48000, BitDepth::B24);
|
||||
tx.send(chunk.clone()).await.unwrap();
|
||||
|
||||
// Le chunk devrait être passé sans modification
|
||||
let received = out_rx.recv().await.unwrap();
|
||||
assert!(Arc::ptr_eq(&chunk, &received));
|
||||
}
|
||||
}
|
||||
@@ -1,500 +0,0 @@
|
||||
//! DiskSink - Écrit le flux audio dans un fichier
|
||||
//!
|
||||
//! Ce module fournit un sink qui écrit les chunks audio sur disque,
|
||||
//! avec support de la dérivation automatique du nom de fichier depuis la source.
|
||||
|
||||
use crate::{events::SourceNameUpdateEvent, nodes::AudioError, AudioChunk};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
|
||||
/// Configuration pour le DiskSink
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DiskSinkConfig {
|
||||
/// Chemin racine où écrire les fichiers
|
||||
pub output_dir: PathBuf,
|
||||
|
||||
/// Nom de fichier explicite (optionnel)
|
||||
/// Si None, sera dérivé du nom de la source
|
||||
pub filename: Option<String>,
|
||||
|
||||
/// Format d'écriture
|
||||
pub format: AudioFileFormat,
|
||||
|
||||
/// Taille du buffer d'écriture (en chunks)
|
||||
pub buffer_size: usize,
|
||||
}
|
||||
|
||||
impl Default for DiskSinkConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
output_dir: PathBuf::from("."),
|
||||
filename: None,
|
||||
format: AudioFileFormat::Wav,
|
||||
buffer_size: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats de fichiers audio supportés
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum AudioFileFormat {
|
||||
/// Format WAV (non compressé)
|
||||
Wav,
|
||||
/// Format FLAC (compressé sans perte)
|
||||
Flac,
|
||||
/// Format brut PCM
|
||||
Raw,
|
||||
}
|
||||
|
||||
impl AudioFileFormat {
|
||||
/// Retourne l'extension de fichier appropriée
|
||||
pub fn extension(&self) -> &str {
|
||||
match self {
|
||||
AudioFileFormat::Wav => "wav",
|
||||
AudioFileFormat::Flac => "flac",
|
||||
AudioFileFormat::Raw => "pcm",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// DiskSink - Écrit le flux audio dans un fichier sur disque
|
||||
///
|
||||
/// Ce sink consomme les chunks audio et les écrit dans un fichier.
|
||||
/// Le nom du fichier peut être dérivé automatiquement du nom de la source
|
||||
/// via les événements `SourceNameUpdateEvent`.
|
||||
///
|
||||
/// # Caractéristiques
|
||||
///
|
||||
/// - Écriture asynchrone avec buffer
|
||||
/// - Dérivation automatique du nom de fichier depuis la source
|
||||
/// - Support de plusieurs formats (WAV, FLAC, PCM brut)
|
||||
/// - Gestion du gain : applique le gain avant l'écriture
|
||||
///
|
||||
/// # Exemples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use pmoaudio::{DiskSink, DiskSinkConfig};
|
||||
/// use std::path::PathBuf;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let config = DiskSinkConfig {
|
||||
/// output_dir: PathBuf::from("/tmp/audio"),
|
||||
/// filename: Some("output.wav".to_string()),
|
||||
/// ..Default::default()
|
||||
/// };
|
||||
///
|
||||
/// let (sink, sink_tx) = DiskSink::new("disk1".to_string(), config, 10);
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// sink.run().await.unwrap()
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
pub struct DiskSink {
|
||||
/// Identifiant du sink
|
||||
node_id: String,
|
||||
|
||||
/// Channel pour recevoir les chunks audio
|
||||
rx: mpsc::Receiver<Arc<AudioChunk>>,
|
||||
|
||||
/// Configuration
|
||||
config: DiskSinkConfig,
|
||||
|
||||
/// Nom de fichier résolu (partagé)
|
||||
resolved_filename: Arc<RwLock<Option<PathBuf>>>,
|
||||
|
||||
/// Receiver pour les événements de nom de source (optionnel)
|
||||
source_name_rx: Option<mpsc::Receiver<SourceNameUpdateEvent>>,
|
||||
|
||||
/// Writer pour le fichier
|
||||
writer: Option<AudioFileWriter>,
|
||||
}
|
||||
|
||||
impl DiskSink {
|
||||
/// Crée un nouveau DiskSink
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `node_id` - Identifiant unique du sink
|
||||
/// * `config` - Configuration du sink
|
||||
/// * `channel_size` - Taille du buffer du channel
|
||||
pub fn new(
|
||||
node_id: String,
|
||||
config: DiskSinkConfig,
|
||||
channel_size: usize,
|
||||
) -> (Self, mpsc::Sender<Arc<AudioChunk>>) {
|
||||
let (tx, rx) = mpsc::channel(channel_size);
|
||||
|
||||
let sink = Self {
|
||||
node_id,
|
||||
rx,
|
||||
config,
|
||||
resolved_filename: Arc::new(RwLock::new(None)),
|
||||
source_name_rx: None,
|
||||
writer: None,
|
||||
};
|
||||
|
||||
(sink, tx)
|
||||
}
|
||||
|
||||
/// Configure la source des événements de nom de source
|
||||
pub fn set_source_name_source(&mut self, rx: mpsc::Receiver<SourceNameUpdateEvent>) {
|
||||
self.source_name_rx = Some(rx);
|
||||
}
|
||||
|
||||
/// Résout le nom du fichier de sortie
|
||||
///
|
||||
/// Si un filename explicite est fourni dans la config, l'utilise.
|
||||
/// Sinon, utilise le source_name avec l'extension appropriée.
|
||||
fn resolve_filename(&self, source_name: Option<&str>) -> PathBuf {
|
||||
let filename = if let Some(ref explicit_name) = self.config.filename {
|
||||
explicit_name.clone()
|
||||
} else if let Some(name) = source_name {
|
||||
// Nettoyer le nom de la source pour en faire un nom de fichier valide
|
||||
let clean_name = name
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_alphanumeric() || c == '_' || c == '-' {
|
||||
c
|
||||
} else {
|
||||
'_'
|
||||
}
|
||||
})
|
||||
.collect::<String>();
|
||||
|
||||
format!("{}.{}", clean_name, self.config.format.extension())
|
||||
} else {
|
||||
// Fallback sur un nom par défaut
|
||||
format!("{}.{}", self.node_id, self.config.format.extension())
|
||||
};
|
||||
|
||||
self.config.output_dir.join(filename)
|
||||
}
|
||||
|
||||
/// Initialise le writer pour le fichier de sortie
|
||||
async fn initialize_writer(&mut self, source_name: Option<&str>) -> Result<(), AudioError> {
|
||||
let path = self.resolve_filename(source_name);
|
||||
*self.resolved_filename.write().await = Some(path.clone());
|
||||
|
||||
// Créer le répertoire parent si nécessaire
|
||||
if let Some(parent) = path.parent() {
|
||||
tokio::fs::create_dir_all(parent).await.map_err(|e| {
|
||||
AudioError::ProcessingError(format!("Failed to create directory: {}", e))
|
||||
})?;
|
||||
}
|
||||
|
||||
// Créer le writer approprié selon le format
|
||||
let writer = match self.config.format {
|
||||
AudioFileFormat::Wav => AudioFileWriter::new_wav(path).await?,
|
||||
AudioFileFormat::Flac => {
|
||||
// FLAC nécessiterait une bibliothèque externe, pour l'instant utiliser WAV
|
||||
AudioFileWriter::new_wav(path).await?
|
||||
}
|
||||
AudioFileFormat::Raw => AudioFileWriter::new_raw(path).await?,
|
||||
};
|
||||
|
||||
self.writer = Some(writer);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Démarre la boucle de traitement du DiskSink
|
||||
pub async fn run(mut self) -> Result<DiskSinkStats, AudioError> {
|
||||
let mut stats = DiskSinkStats::new(self.node_id.clone());
|
||||
let mut source_name: Option<String> = None;
|
||||
let mut initialized = false;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// Recevoir les chunks audio
|
||||
chunk_opt = self.rx.recv() => {
|
||||
match chunk_opt {
|
||||
Some(chunk) => {
|
||||
// Initialiser le writer à la réception du premier chunk
|
||||
if !initialized {
|
||||
self.initialize_writer(source_name.as_deref()).await?;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// Appliquer le gain avant l'écriture
|
||||
let chunk_with_gain = if chunk.gain_db().abs() > f64::EPSILON {
|
||||
Arc::clone(&chunk).apply_gain()
|
||||
} else {
|
||||
Arc::clone(&chunk)
|
||||
};
|
||||
|
||||
// Écrire le chunk
|
||||
if let Some(ref mut writer) = self.writer {
|
||||
writer.write_chunk(&chunk_with_gain).await?;
|
||||
stats.record_chunk(&chunk_with_gain);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Channel fermé, terminer
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recevoir les mises à jour du nom de source
|
||||
source_event_opt = async {
|
||||
if let Some(ref mut rx) = self.source_name_rx {
|
||||
rx.recv().await
|
||||
} else {
|
||||
std::future::pending().await
|
||||
}
|
||||
} => {
|
||||
if let Some(event) = source_event_opt {
|
||||
source_name = Some(event.source_name.clone());
|
||||
|
||||
// Si on n'a pas encore initialisé, le nom sera utilisé plus tard
|
||||
// Sinon, on pourrait décider de fermer le fichier actuel et d'en créer un nouveau
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fermer le fichier proprement
|
||||
if let Some(writer) = self.writer {
|
||||
writer.close().await?;
|
||||
}
|
||||
|
||||
stats.finalize();
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
/// Writer pour fichiers audio
|
||||
struct AudioFileWriter {
|
||||
file: File,
|
||||
format: AudioFileFormat,
|
||||
sample_rate: Option<u32>,
|
||||
total_samples: usize,
|
||||
}
|
||||
|
||||
impl AudioFileWriter {
|
||||
/// Crée un writer WAV
|
||||
async fn new_wav(path: PathBuf) -> Result<Self, AudioError> {
|
||||
let file = File::create(path)
|
||||
.await
|
||||
.map_err(|e| AudioError::ProcessingError(format!("Failed to create file: {}", e)))?;
|
||||
|
||||
Ok(Self {
|
||||
file,
|
||||
format: AudioFileFormat::Wav,
|
||||
sample_rate: None,
|
||||
total_samples: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Crée un writer pour PCM brut
|
||||
async fn new_raw(path: PathBuf) -> Result<Self, AudioError> {
|
||||
let file = File::create(path)
|
||||
.await
|
||||
.map_err(|e| AudioError::ProcessingError(format!("Failed to create file: {}", e)))?;
|
||||
|
||||
Ok(Self {
|
||||
file,
|
||||
format: AudioFileFormat::Raw,
|
||||
sample_rate: None,
|
||||
total_samples: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Écrit un chunk audio
|
||||
async fn write_chunk(&mut self, chunk: &AudioChunk) -> Result<(), AudioError> {
|
||||
// Enregistrer le sample rate du premier chunk
|
||||
if self.sample_rate.is_none() {
|
||||
let sr = chunk.sample_rate();
|
||||
self.sample_rate = Some(sr);
|
||||
|
||||
// Pour WAV, écrire l'en-tête (simplifié)
|
||||
if matches!(self.format, AudioFileFormat::Wav) {
|
||||
self.write_wav_header(sr).await?;
|
||||
}
|
||||
}
|
||||
|
||||
// Convertir en bytes (little-endian 16-bit PCM)
|
||||
let mut bytes = Vec::with_capacity(chunk.len() * 4);
|
||||
let max_val = chunk.bit_depth().max_value();
|
||||
for frame in chunk.frames() {
|
||||
let left = (frame[0] as f32 / max_val).clamp(-1.0, 1.0);
|
||||
let right = (frame[1] as f32 / max_val).clamp(-1.0, 1.0);
|
||||
let sample_i16 = (left * 32767.0) as i16;
|
||||
bytes.extend_from_slice(&sample_i16.to_le_bytes());
|
||||
let sample_r16 = (right * 32767.0) as i16;
|
||||
bytes.extend_from_slice(&sample_r16.to_le_bytes());
|
||||
}
|
||||
|
||||
self.file.write_all(&bytes).await.map_err(|e| {
|
||||
AudioError::ProcessingError(format!("Failed to write audio data: {}", e))
|
||||
})?;
|
||||
|
||||
self.total_samples += chunk.len();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Écrit un en-tête WAV simplifié
|
||||
async fn write_wav_header(&mut self, sample_rate: u32) -> Result<(), AudioError> {
|
||||
// En-tête WAV basique (sera mis à jour à la fermeture)
|
||||
let mut header = Vec::new();
|
||||
|
||||
// RIFF chunk
|
||||
header.extend_from_slice(b"RIFF");
|
||||
header.extend_from_slice(&0u32.to_le_bytes()); // Taille (à mettre à jour)
|
||||
header.extend_from_slice(b"WAVE");
|
||||
|
||||
// fmt chunk
|
||||
header.extend_from_slice(b"fmt ");
|
||||
header.extend_from_slice(&16u32.to_le_bytes()); // Taille du fmt chunk
|
||||
header.extend_from_slice(&1u16.to_le_bytes()); // Format PCM
|
||||
header.extend_from_slice(&2u16.to_le_bytes()); // 2 canaux (stéréo)
|
||||
header.extend_from_slice(&sample_rate.to_le_bytes());
|
||||
header.extend_from_slice(&(sample_rate * 4).to_le_bytes()); // Byte rate
|
||||
header.extend_from_slice(&4u16.to_le_bytes()); // Block align
|
||||
header.extend_from_slice(&16u16.to_le_bytes()); // Bits per sample
|
||||
|
||||
// data chunk header
|
||||
header.extend_from_slice(b"data");
|
||||
header.extend_from_slice(&0u32.to_le_bytes()); // Taille des données (à mettre à jour)
|
||||
|
||||
self.file.write_all(&header).await.map_err(|e| {
|
||||
AudioError::ProcessingError(format!("Failed to write WAV header: {}", e))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ferme le fichier et met à jour l'en-tête si nécessaire
|
||||
async fn close(mut self) -> Result<(), AudioError> {
|
||||
if matches!(self.format, AudioFileFormat::Wav) {
|
||||
// Mettre à jour les tailles dans l'en-tête WAV
|
||||
let data_size = (self.total_samples * 4) as u32; // 2 bytes per sample * 2 channels
|
||||
let file_size = data_size + 36;
|
||||
|
||||
// Positionner au début et réécrire les tailles
|
||||
use tokio::io::AsyncSeekExt;
|
||||
self.file
|
||||
.seek(std::io::SeekFrom::Start(4))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AudioError::ProcessingError(format!("Failed to seek in file: {}", e))
|
||||
})?;
|
||||
self.file
|
||||
.write_all(&file_size.to_le_bytes())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AudioError::ProcessingError(format!("Failed to update file size: {}", e))
|
||||
})?;
|
||||
|
||||
self.file
|
||||
.seek(std::io::SeekFrom::Start(40))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AudioError::ProcessingError(format!("Failed to seek in file: {}", e))
|
||||
})?;
|
||||
self.file
|
||||
.write_all(&data_size.to_le_bytes())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AudioError::ProcessingError(format!("Failed to update data size: {}", e))
|
||||
})?;
|
||||
}
|
||||
|
||||
self.file
|
||||
.flush()
|
||||
.await
|
||||
.map_err(|e| AudioError::ProcessingError(format!("Failed to flush file: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Statistiques du DiskSink
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DiskSinkStats {
|
||||
pub node_id: String,
|
||||
pub chunks_written: u64,
|
||||
pub total_samples: u64,
|
||||
pub total_duration_sec: f64,
|
||||
}
|
||||
|
||||
impl DiskSinkStats {
|
||||
pub fn new(node_id: String) -> Self {
|
||||
Self {
|
||||
node_id,
|
||||
chunks_written: 0,
|
||||
total_samples: 0,
|
||||
total_duration_sec: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_chunk(&mut self, chunk: &AudioChunk) {
|
||||
self.chunks_written += 1;
|
||||
self.total_samples += chunk.len() as u64;
|
||||
self.total_duration_sec += chunk.len() as f64 / chunk.sample_rate() as f64;
|
||||
}
|
||||
|
||||
pub fn finalize(&mut self) {
|
||||
// Pourrait effectuer des calculs finaux ici
|
||||
}
|
||||
|
||||
pub fn display(&self) {
|
||||
println!("\n=== DiskSink Statistics: {} ===", self.node_id);
|
||||
println!("Chunks written: {}", self.chunks_written);
|
||||
println!("Total samples: {}", self.total_samples);
|
||||
println!("Total duration: {:.3} sec", self.total_duration_sec);
|
||||
println!("============================\n");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::BitDepth;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_disk_sink_basic() {
|
||||
let temp_dir = std::env::temp_dir().join("pmoaudio_test");
|
||||
tokio::fs::create_dir_all(&temp_dir).await.unwrap();
|
||||
|
||||
let config = DiskSinkConfig {
|
||||
output_dir: temp_dir.clone(),
|
||||
filename: Some("test_output.wav".to_string()),
|
||||
format: AudioFileFormat::Wav,
|
||||
buffer_size: 10,
|
||||
};
|
||||
|
||||
let (sink, tx) = DiskSink::new("test".to_string(), config, 10);
|
||||
|
||||
let handle = tokio::spawn(async move { sink.run().await });
|
||||
|
||||
// Envoyer quelques chunks
|
||||
for i in 0..5 {
|
||||
let chunk = AudioChunk::from_channels_f32(
|
||||
i,
|
||||
vec![0.5; 1000],
|
||||
vec![0.5; 1000],
|
||||
48000,
|
||||
BitDepth::B24,
|
||||
);
|
||||
tx.send(chunk).await.unwrap();
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
|
||||
let stats = handle.await.unwrap().unwrap();
|
||||
assert_eq!(stats.chunks_written, 5);
|
||||
|
||||
// Vérifier que le fichier existe
|
||||
let output_path = temp_dir.join("test_output.wav");
|
||||
assert!(output_path.exists());
|
||||
|
||||
// Nettoyage
|
||||
tokio::fs::remove_file(output_path).await.ok();
|
||||
tokio::fs::remove_dir(temp_dir).await.ok();
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
use crate::{
|
||||
nodes::{AudioError, MultiSubscriberNode},
|
||||
AudioChunk,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// DspNode - Applique des transformations DSP aux chunks audio
|
||||
///
|
||||
/// Clone les données uniquement si elles doivent être modifiées
|
||||
pub struct DspNode {
|
||||
rx: mpsc::Receiver<Arc<AudioChunk>>,
|
||||
subscribers: MultiSubscriberNode,
|
||||
gain_db: f32,
|
||||
}
|
||||
|
||||
impl DspNode {
|
||||
pub fn new(channel_size: usize, gain_db: f32) -> (Self, mpsc::Sender<Arc<AudioChunk>>) {
|
||||
let (tx, rx) = mpsc::channel(channel_size);
|
||||
|
||||
let node = Self {
|
||||
rx,
|
||||
subscribers: MultiSubscriberNode::new(),
|
||||
gain_db,
|
||||
};
|
||||
|
||||
(node, tx)
|
||||
}
|
||||
|
||||
pub fn add_subscriber(&mut self, tx: mpsc::Sender<Arc<AudioChunk>>) {
|
||||
self.subscribers.add_subscriber(tx);
|
||||
}
|
||||
|
||||
/// Applique le gain aux chunks
|
||||
pub async fn run(mut self) -> Result<(), AudioError> {
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
if self.gain_db.abs() < f32::EPSILON {
|
||||
// Gain = 0 dB, pas de transformation nécessaire
|
||||
self.subscribers.push(chunk).await?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let gain_linear = AudioChunk::gain_linear_from_db(self.gain_db as f64) as f32;
|
||||
let mut pairs = chunk.to_pairs_f32();
|
||||
for frame in &mut pairs {
|
||||
frame[0] *= gain_linear;
|
||||
frame[1] *= gain_linear;
|
||||
}
|
||||
|
||||
let mut new_chunk = AudioChunk::from_pairs_f32(
|
||||
chunk.order(),
|
||||
pairs,
|
||||
chunk.sample_rate(),
|
||||
chunk.bit_depth(),
|
||||
);
|
||||
if chunk.gain_db().abs() > f64::EPSILON {
|
||||
new_chunk = new_chunk.set_gain_db(chunk.gain_db());
|
||||
}
|
||||
self.subscribers.push(new_chunk).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Met à jour le gain dynamiquement (nécessite un `Arc<RwLock<f32>>` dans une version réelle)
|
||||
pub fn set_gain_db(&mut self, gain_db: f32) {
|
||||
self.gain_db = gain_db;
|
||||
}
|
||||
}
|
||||
|
||||
/// DspNode avec filtre passe-bas simple (mock)
|
||||
#[allow(dead_code)]
|
||||
pub struct LowPassDspNode {
|
||||
rx: mpsc::Receiver<Arc<AudioChunk>>,
|
||||
subscribers: MultiSubscriberNode,
|
||||
alpha: f32, // Coefficient du filtre
|
||||
prev_left: f32,
|
||||
prev_right: f32,
|
||||
}
|
||||
|
||||
impl LowPassDspNode {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(channel_size: usize, cutoff_ratio: f32) -> (Self, mpsc::Sender<Arc<AudioChunk>>) {
|
||||
let (tx, rx) = mpsc::channel(channel_size);
|
||||
|
||||
// Filtre RC simple: alpha = dt / (RC + dt)
|
||||
// cutoff_ratio entre 0 (tout couper) et 1 (tout passer)
|
||||
let alpha = cutoff_ratio.clamp(0.0, 1.0);
|
||||
|
||||
let node = Self {
|
||||
rx,
|
||||
subscribers: MultiSubscriberNode::new(),
|
||||
alpha,
|
||||
prev_left: 0.0,
|
||||
prev_right: 0.0,
|
||||
};
|
||||
|
||||
(node, tx)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn add_subscriber(&mut self, tx: mpsc::Sender<Arc<AudioChunk>>) {
|
||||
self.subscribers.add_subscriber(tx);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn run(mut self) -> Result<(), AudioError> {
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
let pairs = chunk.to_pairs_f32();
|
||||
let mut filtered = Vec::with_capacity(pairs.len());
|
||||
|
||||
for sample in pairs.iter() {
|
||||
self.prev_left = self.prev_left + self.alpha * (sample[0] - self.prev_left);
|
||||
self.prev_right = self.prev_right + self.alpha * (sample[1] - self.prev_right);
|
||||
filtered.push([self.prev_left, self.prev_right]);
|
||||
}
|
||||
|
||||
let mut new_chunk = AudioChunk::from_pairs_f32(
|
||||
chunk.order(),
|
||||
filtered,
|
||||
chunk.sample_rate(),
|
||||
chunk.bit_depth(),
|
||||
);
|
||||
if chunk.gain_db().abs() > f64::EPSILON {
|
||||
new_chunk = new_chunk.set_gain_db(chunk.gain_db());
|
||||
}
|
||||
|
||||
self.subscribers.push(new_chunk).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::BitDepth;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_dsp_node_unity_gain() {
|
||||
let (mut node, tx) = DspNode::new(10, 0.0);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx);
|
||||
|
||||
tokio::spawn(async move {
|
||||
node.run().await.unwrap();
|
||||
});
|
||||
|
||||
// Envoyer un chunk
|
||||
let chunk = AudioChunk::from_channels_f32(
|
||||
0,
|
||||
vec![0.25, 0.5, 0.75],
|
||||
vec![0.1, 0.2, 0.3],
|
||||
48000,
|
||||
BitDepth::B24,
|
||||
);
|
||||
tx.send(chunk.clone()).await.unwrap();
|
||||
|
||||
// Avec gain = 1.0, le chunk ne devrait pas être cloné
|
||||
let received = out_rx.recv().await.unwrap();
|
||||
assert!(Arc::ptr_eq(&chunk, &received));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_dsp_node_gain() {
|
||||
let gain_db = AudioChunk::gain_db_from_linear(2.0) as f32;
|
||||
let (mut node, tx) = DspNode::new(10, gain_db);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx);
|
||||
|
||||
tokio::spawn(async move {
|
||||
node.run().await.unwrap();
|
||||
});
|
||||
|
||||
// Envoyer un chunk
|
||||
let chunk = AudioChunk::from_channels_f32(
|
||||
0,
|
||||
vec![0.25, 0.5, 0.75],
|
||||
vec![0.1, 0.2, 0.3],
|
||||
48000,
|
||||
BitDepth::B24,
|
||||
);
|
||||
tx.send(chunk).await.unwrap();
|
||||
|
||||
// Vérifier que le gain a été appliqué
|
||||
let received = out_rx.recv().await.unwrap();
|
||||
let frames = received.to_pairs_f32();
|
||||
const EPS: f32 = 1e-3;
|
||||
assert!((frames[0][0] - 0.5).abs() < EPS);
|
||||
assert!((frames[1][0] - 1.0).abs() < EPS);
|
||||
assert!((frames[2][0] - 1.0).abs() < EPS); // Clamp at full scale
|
||||
assert!((frames[0][1] - 0.2).abs() < EPS);
|
||||
assert!((frames[1][1] - 0.4).abs() < EPS);
|
||||
assert!((frames[2][1] - 0.6).abs() < EPS);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_lowpass_dsp_node() {
|
||||
let (mut node, tx) = LowPassDspNode::new(10, 0.5);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx);
|
||||
|
||||
tokio::spawn(async move {
|
||||
node.run().await.unwrap();
|
||||
});
|
||||
|
||||
// Envoyer un chunk avec un signal carré
|
||||
let chunk = AudioChunk::from_channels_f32(
|
||||
0,
|
||||
vec![1.0, 1.0, 1.0, -1.0, -1.0, -1.0],
|
||||
vec![1.0, 1.0, 1.0, -1.0, -1.0, -1.0],
|
||||
48000,
|
||||
BitDepth::B24,
|
||||
);
|
||||
tx.send(chunk).await.unwrap();
|
||||
|
||||
// Le filtre devrait lisser le signal
|
||||
let received = out_rx.recv().await.unwrap();
|
||||
let frames = received.to_pairs_f32();
|
||||
assert!(frames[0][0].abs() < 1.0); // Premier échantillon lissé
|
||||
assert!(frames[2][0].abs() < 1.0); // Signal ne devrait pas atteindre 1.0 immédiatement
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_dsp_node_multiple_subscribers() {
|
||||
let gain_db = AudioChunk::gain_db_from_linear(0.5) as f32;
|
||||
let (mut node, tx) = DspNode::new(10, gain_db);
|
||||
let (out_tx1, mut out_rx1) = mpsc::channel(10);
|
||||
let (out_tx2, mut out_rx2) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx1);
|
||||
node.add_subscriber(out_tx2);
|
||||
|
||||
tokio::spawn(async move {
|
||||
node.run().await.unwrap();
|
||||
});
|
||||
|
||||
let chunk =
|
||||
AudioChunk::from_channels_f32(0, vec![0.8, 0.4], vec![0.8, 0.4], 48000, BitDepth::B24);
|
||||
tx.send(chunk).await.unwrap();
|
||||
|
||||
// Les deux abonnés devraient recevoir le même Arc
|
||||
let received1 = out_rx1.recv().await.unwrap();
|
||||
let received2 = out_rx2.recv().await.unwrap();
|
||||
|
||||
assert!(Arc::ptr_eq(&received1, &received2));
|
||||
let frames = received1.to_pairs_f32();
|
||||
const EPS: f32 = 1e-3;
|
||||
assert!((frames[0][0] - 0.4).abs() < EPS); // 0.8 * 0.5
|
||||
assert!((frames[1][0] - 0.2).abs() < EPS); // 0.4 * 0.5
|
||||
}
|
||||
}
|
||||
@@ -1,398 +0,0 @@
|
||||
//! MpdSink - Envoie le flux audio à un démon MPD (Music Player Daemon)
|
||||
//!
|
||||
//! Ce module fournit un sink qui streame l'audio vers un démon MPD distant ou local.
|
||||
//! Note: Cette implémentation est une version mock/skeleton. Une vraie implémentation
|
||||
//! nécessiterait le protocole MPD complet et l'utilisation de bibliothèques comme `mpd`.
|
||||
|
||||
use crate::{nodes::AudioError, AudioChunk};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// Configuration pour le MpdSink
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MpdConfig {
|
||||
/// Adresse du serveur MPD
|
||||
pub host: String,
|
||||
|
||||
/// Port du serveur MPD (défaut: 6600)
|
||||
pub port: u16,
|
||||
|
||||
/// Mot de passe optionnel
|
||||
pub password: Option<String>,
|
||||
|
||||
/// Nom de l'output MPD à utiliser (optionnel)
|
||||
pub output_name: Option<String>,
|
||||
|
||||
/// Taille du buffer
|
||||
pub buffer_size: usize,
|
||||
|
||||
/// Format d'envoi
|
||||
pub format: MpdAudioFormat,
|
||||
}
|
||||
|
||||
impl Default for MpdConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
host: "localhost".to_string(),
|
||||
port: 6600,
|
||||
password: None,
|
||||
output_name: None,
|
||||
buffer_size: 50,
|
||||
format: MpdAudioFormat::S16Le,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats audio supportés par MPD
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum MpdAudioFormat {
|
||||
/// Signed 16-bit Little Endian
|
||||
S16Le,
|
||||
/// Signed 24-bit Little Endian
|
||||
S24Le,
|
||||
/// Signed 32-bit Little Endian
|
||||
S32Le,
|
||||
/// Float 32-bit
|
||||
F32,
|
||||
}
|
||||
|
||||
impl MpdAudioFormat {
|
||||
/// Retourne le nom du format pour le protocole MPD
|
||||
pub fn as_mpd_string(&self) -> &str {
|
||||
match self {
|
||||
MpdAudioFormat::S16Le => "16:16:2",
|
||||
MpdAudioFormat::S24Le => "24:24:2",
|
||||
MpdAudioFormat::S32Le => "32:32:2",
|
||||
MpdAudioFormat::F32 => "f:32:2",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// MpdSink - Streame vers un démon MPD
|
||||
///
|
||||
/// Ce sink se connecte à un serveur MPD et lui envoie le flux audio.
|
||||
/// MPD peut ensuite router l'audio vers différents outputs (ALSA, PulseAudio, HTTP, etc.).
|
||||
///
|
||||
/// # Implémentation actuelle
|
||||
///
|
||||
/// Cette version est un mock qui simule la communication avec MPD.
|
||||
/// Pour une vraie implémentation, il faudrait:
|
||||
/// - Implémenter le protocole MPD (commandes textuelles sur TCP)
|
||||
/// - S'authentifier si nécessaire
|
||||
/// - Configurer le format audio
|
||||
/// - Envoyer les données PCM via le protocole approprié
|
||||
/// - Gérer les commandes de contrôle (play, pause, stop)
|
||||
///
|
||||
/// # Exemples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use pmoaudio::{MpdSink, MpdConfig};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let config = MpdConfig {
|
||||
/// host: "localhost".to_string(),
|
||||
/// port: 6600,
|
||||
/// password: None,
|
||||
/// ..Default::default()
|
||||
/// };
|
||||
///
|
||||
/// let (sink, sink_tx) = MpdSink::new("mpd1".to_string(), config, 10);
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// sink.run().await.unwrap()
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
pub struct MpdSink {
|
||||
/// Identifiant du sink
|
||||
node_id: String,
|
||||
|
||||
/// Channel pour recevoir les chunks audio
|
||||
rx: mpsc::Receiver<Arc<AudioChunk>>,
|
||||
|
||||
/// Configuration
|
||||
config: MpdConfig,
|
||||
|
||||
/// État de la connexion (mock)
|
||||
connected: bool,
|
||||
|
||||
/// Version du serveur MPD (mock)
|
||||
mpd_version: Option<String>,
|
||||
}
|
||||
|
||||
impl MpdSink {
|
||||
/// Crée un nouveau MpdSink
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `node_id` - Identifiant unique du sink
|
||||
/// * `config` - Configuration MPD
|
||||
/// * `channel_size` - Taille du buffer du channel
|
||||
pub fn new(
|
||||
node_id: String,
|
||||
config: MpdConfig,
|
||||
channel_size: usize,
|
||||
) -> (Self, mpsc::Sender<Arc<AudioChunk>>) {
|
||||
let (tx, rx) = mpsc::channel(channel_size);
|
||||
|
||||
let sink = Self {
|
||||
node_id,
|
||||
rx,
|
||||
config,
|
||||
connected: false,
|
||||
mpd_version: None,
|
||||
};
|
||||
|
||||
(sink, tx)
|
||||
}
|
||||
|
||||
/// Établit la connexion avec le serveur MPD (mock)
|
||||
async fn connect(&mut self) -> Result<(), AudioError> {
|
||||
println!(
|
||||
"[{}] Connecting to MPD at {}:{}...",
|
||||
self.node_id, self.config.host, self.config.port
|
||||
);
|
||||
|
||||
// Simuler une connexion TCP
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(300)).await;
|
||||
|
||||
// Dans une vraie implémentation:
|
||||
// 1. Établir connexion TCP
|
||||
// 2. Lire la bannière de version
|
||||
// 3. S'authentifier si password fourni
|
||||
// 4. Configurer le format audio
|
||||
|
||||
self.mpd_version = Some("0.23.0".to_string());
|
||||
self.connected = true;
|
||||
|
||||
println!(
|
||||
"[{}] Connected to MPD v{} successfully",
|
||||
self.node_id,
|
||||
self.mpd_version.as_ref().unwrap()
|
||||
);
|
||||
|
||||
// Configurer le format audio
|
||||
self.configure_audio_format().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Configure le format audio sur MPD (mock)
|
||||
async fn configure_audio_format(&self) -> Result<(), AudioError> {
|
||||
println!(
|
||||
"[{}] Configuring audio format: {}",
|
||||
self.node_id,
|
||||
self.config.format.as_mpd_string()
|
||||
);
|
||||
|
||||
// Dans une vraie implémentation:
|
||||
// Envoyer une commande MPD pour configurer le format
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Envoie un chunk au serveur MPD (mock)
|
||||
async fn send_chunk(&self, _chunk: &AudioChunk) -> Result<(), AudioError> {
|
||||
if !self.connected {
|
||||
return Err(AudioError::ProcessingError(
|
||||
"Not connected to MPD".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Dans une vraie implémentation:
|
||||
// 1. Appliquer le gain
|
||||
// 2. Convertir dans le format approprié (S16LE, etc.)
|
||||
// 3. Envoyer via le protocole MPD (probablement via une commande `sendmessage` ou pipe)
|
||||
|
||||
// Simuler un délai d'envoi
|
||||
tokio::time::sleep(tokio::time::Duration::from_micros(50)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Déconnecte proprement du serveur MPD (mock)
|
||||
async fn disconnect(&mut self) -> Result<(), AudioError> {
|
||||
if self.connected {
|
||||
println!("[{}] Disconnecting from MPD...", self.node_id);
|
||||
|
||||
// Dans une vraie implémentation:
|
||||
// Envoyer la commande "close"
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||
|
||||
self.connected = false;
|
||||
|
||||
println!("[{}] Disconnected successfully", self.node_id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Démarre la boucle de traitement du MpdSink
|
||||
pub async fn run(mut self) -> Result<MpdStats, AudioError> {
|
||||
// Établir la connexion
|
||||
self.connect().await?;
|
||||
|
||||
let mut stats = MpdStats::new(
|
||||
self.node_id.clone(),
|
||||
format!("{}:{}", self.config.host, self.config.port),
|
||||
);
|
||||
|
||||
// Boucle principale
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
// Appliquer le gain si nécessaire
|
||||
let chunk_to_send = if chunk.gain_db().abs() > f64::EPSILON {
|
||||
Arc::clone(&chunk).apply_gain()
|
||||
} else {
|
||||
Arc::clone(&chunk)
|
||||
};
|
||||
|
||||
// Envoyer au serveur MPD
|
||||
self.send_chunk(&chunk_to_send).await?;
|
||||
|
||||
stats.record_chunk(&chunk_to_send);
|
||||
}
|
||||
|
||||
// Déconnexion propre
|
||||
self.disconnect().await?;
|
||||
|
||||
stats.finalize();
|
||||
Ok(stats)
|
||||
}
|
||||
|
||||
/// Retourne un handle pour contrôler le sink (mock)
|
||||
pub fn get_handle(&self) -> MpdHandle {
|
||||
MpdHandle {
|
||||
node_id: self.node_id.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle pour contrôler le MpdSink
|
||||
///
|
||||
/// Permet d'envoyer des commandes de contrôle au serveur MPD
|
||||
#[derive(Clone)]
|
||||
pub struct MpdHandle {
|
||||
node_id: String,
|
||||
}
|
||||
|
||||
impl MpdHandle {
|
||||
/// Commande play (mock)
|
||||
pub async fn play(&self) -> Result<(), AudioError> {
|
||||
println!("[{}] MPD command: play", self.node_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Commande pause (mock)
|
||||
pub async fn pause(&self) -> Result<(), AudioError> {
|
||||
println!("[{}] MPD command: pause", self.node_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Commande stop (mock)
|
||||
pub async fn stop(&self) -> Result<(), AudioError> {
|
||||
println!("[{}] MPD command: stop", self.node_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Change le volume MPD (0-100) (mock)
|
||||
pub async fn set_volume(&self, volume: u8) -> Result<(), AudioError> {
|
||||
let clamped = volume.min(100);
|
||||
println!("[{}] MPD command: setvol {}", self.node_id, clamped);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Statistiques du MpdSink
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MpdStats {
|
||||
pub node_id: String,
|
||||
pub server_address: String,
|
||||
pub chunks_sent: u64,
|
||||
pub total_samples: u64,
|
||||
pub total_duration_sec: f64,
|
||||
}
|
||||
|
||||
impl MpdStats {
|
||||
pub fn new(node_id: String, server_address: String) -> Self {
|
||||
Self {
|
||||
node_id,
|
||||
server_address,
|
||||
chunks_sent: 0,
|
||||
total_samples: 0,
|
||||
total_duration_sec: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_chunk(&mut self, chunk: &AudioChunk) {
|
||||
self.chunks_sent += 1;
|
||||
self.total_samples += chunk.len() as u64;
|
||||
self.total_duration_sec += chunk.len() as f64 / chunk.sample_rate() as f64;
|
||||
}
|
||||
|
||||
pub fn finalize(&mut self) {
|
||||
// Calculs finaux si nécessaire
|
||||
}
|
||||
|
||||
pub fn display(&self) {
|
||||
println!("\n=== MPD Sink Statistics: {} ===", self.node_id);
|
||||
println!("Server: {}", self.server_address);
|
||||
println!("Chunks sent: {}", self.chunks_sent);
|
||||
println!("Total samples: {}", self.total_samples);
|
||||
println!("Total duration: {:.3} sec", self.total_duration_sec);
|
||||
println!("===============================\n");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::BitDepth;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_mpd_sink_basic() {
|
||||
let config = MpdConfig {
|
||||
host: "localhost".to_string(),
|
||||
port: 6600,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (sink, tx) = MpdSink::new("test".to_string(), config, 10);
|
||||
|
||||
let handle = tokio::spawn(async move { sink.run().await });
|
||||
|
||||
// Envoyer quelques chunks
|
||||
for i in 0..5 {
|
||||
let chunk = AudioChunk::from_channels_f32(
|
||||
i,
|
||||
vec![0.5; 1000],
|
||||
vec![0.5; 1000],
|
||||
48000,
|
||||
BitDepth::B24,
|
||||
);
|
||||
tx.send(chunk).await.unwrap();
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
|
||||
let stats = handle.await.unwrap().unwrap();
|
||||
assert_eq!(stats.chunks_sent, 5);
|
||||
assert_eq!(stats.server_address, "localhost:6600");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_mpd_handle() {
|
||||
let config = MpdConfig::default();
|
||||
let (sink, _tx) = MpdSink::new("test".to_string(), config, 10);
|
||||
|
||||
let handle = sink.get_handle();
|
||||
|
||||
// Tester les commandes (mock)
|
||||
handle.play().await.unwrap();
|
||||
handle.pause().await.unwrap();
|
||||
handle.set_volume(75).await.unwrap();
|
||||
handle.stop().await.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
use crate::{nodes::AudioError, AudioChunk};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// SinkNode - Node terminal qui consomme les chunks audio
|
||||
///
|
||||
/// Version mock pour tests et logging
|
||||
pub struct SinkNode {
|
||||
rx: mpsc::Receiver<Arc<AudioChunk>>,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl SinkNode {
|
||||
pub fn new(name: String, channel_size: usize) -> (Self, mpsc::Sender<Arc<AudioChunk>>) {
|
||||
let (tx, rx) = mpsc::channel(channel_size);
|
||||
|
||||
let node = Self { rx, name };
|
||||
|
||||
(node, tx)
|
||||
}
|
||||
|
||||
/// Version silencieuse - consomme les chunks sans action
|
||||
pub async fn run_silent(mut self) -> Result<(), AudioError> {
|
||||
while let Some(_chunk) = self.rx.recv().await {
|
||||
// Ne rien faire, juste consommer
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Version avec logging
|
||||
pub async fn run_with_logging(mut self) -> Result<(), AudioError> {
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
println!(
|
||||
"[{}] Received chunk #{} - {} samples @ {} Hz",
|
||||
self.name,
|
||||
chunk.order(),
|
||||
chunk.len(),
|
||||
chunk.sample_rate()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Version avec statistiques
|
||||
pub async fn run_with_stats(mut self) -> Result<SinkStats, AudioError> {
|
||||
let mut stats = SinkStats::new(self.name.clone());
|
||||
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
stats.process_chunk(&chunk);
|
||||
}
|
||||
|
||||
Ok(stats)
|
||||
}
|
||||
|
||||
/// Version mock pour écriture dans un fichier (simule l'écriture)
|
||||
pub async fn run_mock_file_writer(mut self) -> Result<usize, AudioError> {
|
||||
let mut total_samples = 0;
|
||||
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
total_samples += chunk.len();
|
||||
// Simuler l'écriture avec un petit délai
|
||||
tokio::time::sleep(tokio::time::Duration::from_micros(10)).await;
|
||||
}
|
||||
|
||||
Ok(total_samples)
|
||||
}
|
||||
}
|
||||
|
||||
/// Statistiques collectées par un SinkNode
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SinkStats {
|
||||
pub name: String,
|
||||
pub chunks_received: u64,
|
||||
pub total_samples: u64,
|
||||
pub total_duration_sec: f64,
|
||||
pub peak_left: f32,
|
||||
pub peak_right: f32,
|
||||
pub rms_left: f64,
|
||||
pub rms_right: f64,
|
||||
}
|
||||
|
||||
impl SinkStats {
|
||||
pub fn new(name: String) -> Self {
|
||||
Self {
|
||||
name,
|
||||
chunks_received: 0,
|
||||
total_samples: 0,
|
||||
total_duration_sec: 0.0,
|
||||
peak_left: 0.0,
|
||||
peak_right: 0.0,
|
||||
rms_left: 0.0,
|
||||
rms_right: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_chunk(&mut self, chunk: &AudioChunk) {
|
||||
self.chunks_received += 1;
|
||||
let len = chunk.len() as u64;
|
||||
self.total_samples += len;
|
||||
self.total_duration_sec += chunk.len() as f64 / chunk.sample_rate() as f64;
|
||||
|
||||
let inv_max = 1.0f32 / chunk.bit_depth().max_value();
|
||||
let mut peak_left = self.peak_left;
|
||||
let mut peak_right = self.peak_right;
|
||||
let mut sum_squares_left = 0.0f64;
|
||||
let mut sum_squares_right = 0.0f64;
|
||||
|
||||
for frame in chunk.frames() {
|
||||
let left = frame[0] as f32 * inv_max;
|
||||
let right = frame[1] as f32 * inv_max;
|
||||
let left_abs = left.abs();
|
||||
let right_abs = right.abs();
|
||||
if left_abs > peak_left {
|
||||
peak_left = left_abs;
|
||||
}
|
||||
if right_abs > peak_right {
|
||||
peak_right = right_abs;
|
||||
}
|
||||
let l64 = left as f64;
|
||||
let r64 = right as f64;
|
||||
sum_squares_left += l64 * l64;
|
||||
sum_squares_right += r64 * r64;
|
||||
}
|
||||
|
||||
self.peak_left = peak_left;
|
||||
self.peak_right = peak_right;
|
||||
|
||||
let prev_samples = self.total_samples - len;
|
||||
self.rms_left = ((self.rms_left.powi(2) * prev_samples as f64 + sum_squares_left)
|
||||
/ self.total_samples as f64)
|
||||
.sqrt();
|
||||
self.rms_right = ((self.rms_right.powi(2) * prev_samples as f64 + sum_squares_right)
|
||||
/ self.total_samples as f64)
|
||||
.sqrt();
|
||||
}
|
||||
|
||||
pub fn display(&self) {
|
||||
println!("\n=== Sink Statistics: {} ===", self.name);
|
||||
println!("Chunks received: {}", self.chunks_received);
|
||||
println!("Total samples: {}", self.total_samples);
|
||||
println!("Total duration: {:.3} sec", self.total_duration_sec);
|
||||
println!("Peak L/R: {:.3} / {:.3}", self.peak_left, self.peak_right);
|
||||
println!("RMS L/R: {:.3} / {:.3}", self.rms_left, self.rms_right);
|
||||
println!("========================\n");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::BitDepth;
|
||||
|
||||
const BD: BitDepth = BitDepth::B24;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sink_node_silent() {
|
||||
let (node, tx) = SinkNode::new("test".to_string(), 10);
|
||||
|
||||
let handle = tokio::spawn(async move { node.run_silent().await });
|
||||
|
||||
// Envoyer quelques chunks
|
||||
for i in 0..3 {
|
||||
let chunk = AudioChunk::from_channels_f32(i, vec![0.0; 100], vec![0.0; 100], 48000, BD);
|
||||
tx.send(chunk).await.unwrap();
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
handle.await.unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sink_node_stats() {
|
||||
let (node, tx) = SinkNode::new("test".to_string(), 10);
|
||||
|
||||
let handle = tokio::spawn(async move { node.run_with_stats().await });
|
||||
|
||||
// Envoyer des chunks avec signal connu
|
||||
for i in 0..3 {
|
||||
let chunk =
|
||||
AudioChunk::from_channels_f32(i, vec![1.0; 1000], vec![0.5; 1000], 48000, BD);
|
||||
tx.send(chunk).await.unwrap();
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
let stats = handle.await.unwrap().unwrap();
|
||||
|
||||
assert_eq!(stats.chunks_received, 3);
|
||||
assert_eq!(stats.total_samples, 3000);
|
||||
assert!((stats.peak_left - 1.0).abs() < 1e-6);
|
||||
assert!((stats.peak_right - 0.5).abs() < 1e-6);
|
||||
assert!((stats.rms_left - 1.0).abs() < 0.001);
|
||||
assert!((stats.rms_right - 0.5).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sink_node_file_writer() {
|
||||
let (node, tx) = SinkNode::new("writer".to_string(), 10);
|
||||
|
||||
let handle = tokio::spawn(async move { node.run_mock_file_writer().await });
|
||||
|
||||
// Envoyer des chunks
|
||||
for i in 0..5 {
|
||||
let chunk = AudioChunk::from_channels_f32(i, vec![0.0; 100], vec![0.0; 100], 48000, BD);
|
||||
tx.send(chunk).await.unwrap();
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
let total_samples = handle.await.unwrap().unwrap();
|
||||
|
||||
assert_eq!(total_samples, 500);
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
use crate::{
|
||||
nodes::{AudioError, MultiSubscriberNode},
|
||||
AudioChunk, BitDepth,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// SourceNode - Génère ou lit des chunks audio depuis une source
|
||||
///
|
||||
/// Ce node est la source du pipeline. Version mock pour tests.
|
||||
pub struct SourceNode {
|
||||
subscribers: MultiSubscriberNode,
|
||||
}
|
||||
|
||||
const DEFAULT_BIT_DEPTH: BitDepth = BitDepth::B24;
|
||||
|
||||
impl SourceNode {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
subscribers: MultiSubscriberNode::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_subscriber(&mut self, tx: mpsc::Sender<Arc<AudioChunk>>) {
|
||||
self.subscribers.add_subscriber(tx);
|
||||
}
|
||||
|
||||
/// Génère un chunk de test avec une forme d'onde sinusoïdale
|
||||
pub fn generate_test_chunk(
|
||||
order: u64,
|
||||
size: usize,
|
||||
sample_rate: u32,
|
||||
frequency: f32,
|
||||
) -> Arc<AudioChunk> {
|
||||
let mut left = Vec::with_capacity(size);
|
||||
let mut right = Vec::with_capacity(size);
|
||||
|
||||
for i in 0..size {
|
||||
let t = (order * size as u64 + i as u64) as f32 / sample_rate as f32;
|
||||
let sample = (2.0 * std::f32::consts::PI * frequency * t).sin();
|
||||
left.push(sample);
|
||||
right.push(sample * 0.8); // Légèrement différent pour la stéréo
|
||||
}
|
||||
|
||||
AudioChunk::from_channels_f32(order, left, right, sample_rate, DEFAULT_BIT_DEPTH)
|
||||
}
|
||||
|
||||
/// Génère et envoie des chunks de test
|
||||
pub async fn generate_chunks(
|
||||
&self,
|
||||
count: u64,
|
||||
chunk_size: usize,
|
||||
sample_rate: u32,
|
||||
frequency: f32,
|
||||
) -> Result<(), AudioError> {
|
||||
for i in 0..count {
|
||||
let chunk = Self::generate_test_chunk(i, chunk_size, sample_rate, frequency);
|
||||
self.subscribers.push(chunk).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Génère des chunks silencieux
|
||||
pub async fn generate_silence(
|
||||
&self,
|
||||
count: u64,
|
||||
chunk_size: usize,
|
||||
sample_rate: u32,
|
||||
) -> Result<(), AudioError> {
|
||||
for i in 0..count {
|
||||
let stereo = vec![[0i32; 2]; chunk_size];
|
||||
let chunk = AudioChunk::new(i, stereo, sample_rate, DEFAULT_BIT_DEPTH);
|
||||
self.subscribers.push(chunk).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Version streaming : génère des chunks continuellement avec délai
|
||||
pub async fn stream_chunks(
|
||||
&self,
|
||||
chunk_size: usize,
|
||||
sample_rate: u32,
|
||||
frequency: f32,
|
||||
duration_ms: u64,
|
||||
) -> Result<(), AudioError> {
|
||||
let chunk_duration_ms = (chunk_size as f64 / sample_rate as f64 * 1000.0) as u64;
|
||||
let mut order = 0u64;
|
||||
|
||||
let start = tokio::time::Instant::now();
|
||||
let duration = tokio::time::Duration::from_millis(duration_ms);
|
||||
|
||||
while start.elapsed() < duration {
|
||||
let chunk = Self::generate_test_chunk(order, chunk_size, sample_rate, frequency);
|
||||
self.subscribers.push(chunk).await?;
|
||||
|
||||
order += 1;
|
||||
|
||||
// Attendre pour simuler le timing réel
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(chunk_duration_ms)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SourceNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_source_node_generation() {
|
||||
let mut source = SourceNode::new();
|
||||
let (tx, mut rx) = mpsc::channel(10);
|
||||
|
||||
source.add_subscriber(tx);
|
||||
|
||||
// Générer 3 chunks
|
||||
source.generate_chunks(3, 100, 48000, 440.0).await.unwrap();
|
||||
|
||||
// Vérifier la réception
|
||||
for i in 0..3 {
|
||||
let chunk = rx.recv().await.unwrap();
|
||||
assert_eq!(chunk.order(), i);
|
||||
assert_eq!(chunk.len(), 100);
|
||||
assert_eq!(chunk.sample_rate(), 48000);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sine_wave_generation() {
|
||||
let chunk = SourceNode::generate_test_chunk(0, 48000, 48000, 440.0);
|
||||
|
||||
// Vérifier qu'on a bien une sinusoïde
|
||||
// À 440 Hz avec 48000 samples/s, on devrait avoir 440 cycles
|
||||
let pairs = chunk.to_pairs_f32();
|
||||
let left: Vec<f32> = pairs.iter().map(|frame| frame[0]).collect();
|
||||
|
||||
// Trouver les passages par zéro
|
||||
let mut zero_crossings = 0;
|
||||
for i in 1..left.len() {
|
||||
if (left[i - 1] < 0.0 && left[i] >= 0.0) || (left[i - 1] >= 0.0 && left[i] < 0.0) {
|
||||
zero_crossings += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 440 cycles = 880 passages par zéro (approximativement)
|
||||
assert!(zero_crossings > 850 && zero_crossings < 910);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_source_node_silence() {
|
||||
let mut source = SourceNode::new();
|
||||
let (tx, mut rx) = mpsc::channel(10);
|
||||
|
||||
source.add_subscriber(tx);
|
||||
|
||||
source.generate_silence(2, 100, 48000).await.unwrap();
|
||||
|
||||
for _ in 0..2 {
|
||||
let chunk = rx.recv().await.unwrap();
|
||||
assert!(chunk.frames().iter().all(|frame| frame[0] == 0));
|
||||
assert!(chunk.frames().iter().all(|frame| frame[1] == 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
use crate::{
|
||||
nodes::{AudioError, MultiSubscriberNode},
|
||||
AudioChunk,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
|
||||
/// TimerNode - Node passthrough qui calcule la position temporelle
|
||||
///
|
||||
/// Ce node ne modifie pas les données audio, il les passe directement
|
||||
/// aux abonnés tout en maintenant un compteur de samples pour calculer
|
||||
/// la position en secondes.
|
||||
///
|
||||
/// # Fonctionnement
|
||||
///
|
||||
/// Pour chaque chunk reçu:
|
||||
/// 1. Incrémente `elapsed_samples += chunk.len()`
|
||||
/// 2. Calcule `position_sec = elapsed_samples / sample_rate`
|
||||
/// 3. Push le chunk (sans modification) vers les abonnés
|
||||
///
|
||||
/// # Utilisation
|
||||
///
|
||||
/// Le TimerNode fournit un [`TimerHandle`] qui permet de lire la position
|
||||
/// depuis d'autres threads/tasks sans bloquer le pipeline.
|
||||
///
|
||||
/// # Exemples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use pmoaudio::TimerNode;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (mut timer, timer_tx) = TimerNode::new(10);
|
||||
/// let handle = timer.get_position_handle();
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// timer.run().await.unwrap();
|
||||
/// });
|
||||
///
|
||||
/// // Lire la position depuis un autre thread
|
||||
/// let position = handle.position_sec().await;
|
||||
/// println!("Position: {:.2} sec", position);
|
||||
/// }
|
||||
/// ```
|
||||
pub struct TimerNode {
|
||||
rx: mpsc::Receiver<Arc<AudioChunk>>,
|
||||
subscribers: MultiSubscriberNode,
|
||||
elapsed_samples: Arc<RwLock<u64>>,
|
||||
current_sample_rate: Arc<RwLock<u32>>,
|
||||
}
|
||||
|
||||
impl TimerNode {
|
||||
/// Crée un nouveau TimerNode
|
||||
pub fn new(channel_size: usize) -> (Self, mpsc::Sender<Arc<AudioChunk>>) {
|
||||
let (tx, rx) = mpsc::channel(channel_size);
|
||||
|
||||
let node = Self {
|
||||
rx,
|
||||
subscribers: MultiSubscriberNode::new(),
|
||||
elapsed_samples: Arc::new(RwLock::new(0)),
|
||||
current_sample_rate: Arc::new(RwLock::new(48000)), // Default
|
||||
};
|
||||
|
||||
(node, tx)
|
||||
}
|
||||
|
||||
/// Ajoute un abonné
|
||||
pub fn add_subscriber(&mut self, tx: mpsc::Sender<Arc<AudioChunk>>) {
|
||||
self.subscribers.add_subscriber(tx);
|
||||
}
|
||||
|
||||
/// Retourne la position actuelle en secondes
|
||||
pub async fn position_sec(&self) -> f64 {
|
||||
let elapsed = *self.elapsed_samples.read().await;
|
||||
let sample_rate = *self.current_sample_rate.read().await;
|
||||
elapsed as f64 / sample_rate as f64
|
||||
}
|
||||
|
||||
/// Retourne le nombre total d'échantillons écoulés
|
||||
pub async fn elapsed_samples(&self) -> u64 {
|
||||
*self.elapsed_samples.read().await
|
||||
}
|
||||
|
||||
/// Reset le compteur
|
||||
pub async fn reset(&self) {
|
||||
let mut elapsed = self.elapsed_samples.write().await;
|
||||
*elapsed = 0;
|
||||
}
|
||||
|
||||
/// Démarre la boucle de traitement du TimerNode
|
||||
pub async fn run(mut self) -> Result<(), AudioError> {
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
// Mettre à jour le sample rate si nécessaire
|
||||
{
|
||||
let mut sr = self.current_sample_rate.write().await;
|
||||
if *sr != chunk.sample_rate() {
|
||||
*sr = chunk.sample_rate();
|
||||
}
|
||||
}
|
||||
|
||||
// Incrémenter le compteur d'échantillons
|
||||
{
|
||||
let mut elapsed = self.elapsed_samples.write().await;
|
||||
*elapsed += chunk.len() as u64;
|
||||
}
|
||||
|
||||
// Push immédiatement le même chunk vers les abonnés (passthrough)
|
||||
self.subscribers.push(chunk).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Version non-bloquante avec try_push
|
||||
pub async fn run_nonblocking(mut self) -> Result<(), AudioError> {
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
{
|
||||
let mut sr = self.current_sample_rate.write().await;
|
||||
if *sr != chunk.sample_rate() {
|
||||
*sr = chunk.sample_rate();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut elapsed = self.elapsed_samples.write().await;
|
||||
*elapsed += chunk.len() as u64;
|
||||
}
|
||||
|
||||
self.subscribers.try_push(chunk).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retourne un handle pour lire la position depuis d'autres threads
|
||||
pub fn get_position_handle(&self) -> TimerHandle {
|
||||
TimerHandle {
|
||||
elapsed_samples: self.elapsed_samples.clone(),
|
||||
current_sample_rate: self.current_sample_rate.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle pour lire la position du TimerNode depuis d'autres threads
|
||||
///
|
||||
/// Ce handle peut être cloné et utilisé depuis plusieurs threads/tasks
|
||||
/// pour monitorer la position de lecture sans bloquer le pipeline.
|
||||
///
|
||||
/// # Exemples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use pmoaudio::TimerNode;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (mut timer, _tx) = TimerNode::new(10);
|
||||
/// let handle = timer.get_position_handle();
|
||||
/// let handle_clone = handle.clone();
|
||||
///
|
||||
/// // Utiliser depuis plusieurs tasks
|
||||
/// tokio::spawn(async move {
|
||||
/// loop {
|
||||
/// let pos = handle_clone.position_sec().await;
|
||||
/// println!("Position: {:.2}s", pos);
|
||||
/// tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
/// }
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct TimerHandle {
|
||||
elapsed_samples: Arc<RwLock<u64>>,
|
||||
current_sample_rate: Arc<RwLock<u32>>,
|
||||
}
|
||||
|
||||
impl TimerHandle {
|
||||
/// Retourne la position actuelle en secondes
|
||||
pub async fn position_sec(&self) -> f64 {
|
||||
let elapsed = *self.elapsed_samples.read().await;
|
||||
let sample_rate = *self.current_sample_rate.read().await;
|
||||
elapsed as f64 / sample_rate as f64
|
||||
}
|
||||
|
||||
/// Retourne le nombre total d'échantillons écoulés
|
||||
pub async fn elapsed_samples(&self) -> u64 {
|
||||
*self.elapsed_samples.read().await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::BitDepth;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_timer_node_position_calculation() {
|
||||
let (mut node, tx) = TimerNode::new(10);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx);
|
||||
|
||||
let handle = node.get_position_handle();
|
||||
|
||||
// Spawn le node
|
||||
tokio::spawn(async move {
|
||||
node.run().await.unwrap();
|
||||
});
|
||||
|
||||
// Envoyer 3 chunks de 1000 samples à 48000 Hz
|
||||
for i in 0..3 {
|
||||
let stereo = vec![[0i32; 2]; 1000];
|
||||
let chunk = AudioChunk::new(i, stereo, 48000, BitDepth::B24);
|
||||
tx.send(chunk).await.unwrap();
|
||||
}
|
||||
|
||||
// Attendre que les chunks soient traités
|
||||
for _ in 0..3 {
|
||||
out_rx.recv().await.unwrap();
|
||||
}
|
||||
|
||||
// Vérifier la position
|
||||
let position = handle.position_sec().await;
|
||||
let expected = 3000.0 / 48000.0; // 3 chunks * 1000 samples / 48000 Hz
|
||||
assert!((position - expected).abs() < 0.0001);
|
||||
|
||||
let elapsed = handle.elapsed_samples().await;
|
||||
assert_eq!(elapsed, 3000);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_timer_node_passthrough() {
|
||||
let (mut node, tx) = TimerNode::new(10);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx);
|
||||
|
||||
tokio::spawn(async move {
|
||||
node.run().await.unwrap();
|
||||
});
|
||||
|
||||
// Envoyer un chunk
|
||||
let chunk = AudioChunk::from_channels_i32(
|
||||
42,
|
||||
vec![100, 200, 300],
|
||||
vec![400, 500, 600],
|
||||
48000,
|
||||
BitDepth::B24,
|
||||
);
|
||||
tx.send(chunk.clone()).await.unwrap();
|
||||
|
||||
// Recevoir le chunk
|
||||
let received = out_rx.recv().await.unwrap();
|
||||
|
||||
// Vérifier que c'est le même Arc (pas de clone des données)
|
||||
assert!(Arc::ptr_eq(&chunk, &received));
|
||||
assert_eq!(received.order(), 42);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_timer_node_sample_rate_change() {
|
||||
let (mut node, tx) = TimerNode::new(10);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx);
|
||||
|
||||
let handle = node.get_position_handle();
|
||||
|
||||
tokio::spawn(async move {
|
||||
node.run().await.unwrap();
|
||||
});
|
||||
|
||||
// Chunk à 48000 Hz
|
||||
let chunk1 = AudioChunk::new(0, vec![[0i32; 2]; 48000], 48000, BitDepth::B24);
|
||||
tx.send(chunk1).await.unwrap();
|
||||
out_rx.recv().await.unwrap();
|
||||
|
||||
// Après 48000 samples à 48000 Hz = 1 seconde
|
||||
let pos1 = handle.position_sec().await;
|
||||
assert!((pos1 - 1.0).abs() < 0.0001);
|
||||
|
||||
// Chunk à 96000 Hz
|
||||
let chunk2 = AudioChunk::new(1, vec![[0i32; 2]; 96000], 96000, BitDepth::B24);
|
||||
tx.send(chunk2).await.unwrap();
|
||||
out_rx.recv().await.unwrap();
|
||||
|
||||
// Position calculée avec le nouveau sample rate
|
||||
let pos2 = handle.position_sec().await;
|
||||
let expected = (48000.0 + 96000.0) / 96000.0;
|
||||
assert!((pos2 - expected).abs() < 0.0001);
|
||||
}
|
||||
}
|
||||
@@ -1,363 +0,0 @@
|
||||
//! Volume nodes - Contrôle du volume audio
|
||||
//!
|
||||
//! Ce module fournit des nodes pour ajuster le volume du flux audio,
|
||||
//! avec support du volume master/secondaire et notification des changements.
|
||||
|
||||
use crate::{
|
||||
events::{EventPublisher, VolumeChangeEvent},
|
||||
nodes::{AudioError, MultiSubscriberNode},
|
||||
AudioChunk,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
|
||||
/// VolumeNode - Applique un gain au flux audio (contrôle software)
|
||||
///
|
||||
/// Ce node modifie le champ `gain` de chaque `AudioChunk` qui le traverse.
|
||||
/// Le gain est multiplié avec le gain existant du chunk, permettant ainsi
|
||||
/// une chaîne de contrôles de volume.
|
||||
///
|
||||
/// # Caractéristiques
|
||||
///
|
||||
/// - Thread-safe : le volume peut être modifié pendant l'exécution via `set_volume`
|
||||
/// - Notification : émet des événements `VolumeChangeEvent` lors des changements
|
||||
/// - Master/Slave : peut s'abonner à un volume master pour synchronisation
|
||||
///
|
||||
/// # Exemples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use pmoaudio::VolumeNode;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (volume_node, volume_tx) = VolumeNode::new("Room 1".to_string(), 0.8, 10);
|
||||
///
|
||||
/// // Modifier le volume pendant l'exécution
|
||||
/// let handle = volume_node.get_handle();
|
||||
/// tokio::spawn(async move {
|
||||
/// tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
/// handle.set_volume(0.5).await;
|
||||
/// });
|
||||
///
|
||||
/// tokio::spawn(async move { volume_node.run().await.unwrap() });
|
||||
/// }
|
||||
/// ```
|
||||
pub struct VolumeNode {
|
||||
/// Channel pour recevoir les chunks audio
|
||||
rx: mpsc::Receiver<Arc<AudioChunk>>,
|
||||
|
||||
/// Subscribers pour les chunks modifiés
|
||||
subscribers: MultiSubscriberNode,
|
||||
|
||||
/// Volume courant (partagé via RwLock pour lecture/écriture thread-safe)
|
||||
volume: Arc<RwLock<f32>>,
|
||||
|
||||
/// Publisher pour les événements de changement de volume
|
||||
volume_publisher: EventPublisher<VolumeChangeEvent>,
|
||||
|
||||
/// Identifiant unique du node (pour traçabilité)
|
||||
node_id: String,
|
||||
|
||||
/// Receiver pour les événements de volume master (optionnel)
|
||||
master_volume_rx: Option<mpsc::Receiver<VolumeChangeEvent>>,
|
||||
}
|
||||
|
||||
impl VolumeNode {
|
||||
/// Crée un nouveau VolumeNode
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `node_id` - Identifiant unique du node
|
||||
/// * `initial_volume` - Volume initial (0.0 à 1.0)
|
||||
/// * `channel_size` - Taille du buffer du channel
|
||||
pub fn new(
|
||||
node_id: String,
|
||||
initial_volume: f32,
|
||||
channel_size: usize,
|
||||
) -> (Self, mpsc::Sender<Arc<AudioChunk>>) {
|
||||
let (tx, rx) = mpsc::channel(channel_size);
|
||||
|
||||
let node = Self {
|
||||
rx,
|
||||
subscribers: MultiSubscriberNode::new(),
|
||||
volume: Arc::new(RwLock::new(initial_volume)),
|
||||
volume_publisher: EventPublisher::new(),
|
||||
node_id,
|
||||
master_volume_rx: None,
|
||||
};
|
||||
|
||||
(node, tx)
|
||||
}
|
||||
|
||||
/// Ajoute un subscriber pour recevoir les chunks audio modifiés
|
||||
pub fn add_subscriber(&mut self, tx: mpsc::Sender<Arc<AudioChunk>>) {
|
||||
self.subscribers.add_subscriber(tx);
|
||||
}
|
||||
|
||||
/// Ajoute un subscriber pour les événements de changement de volume
|
||||
pub fn subscribe_volume_events(&mut self, tx: mpsc::Sender<VolumeChangeEvent>) {
|
||||
self.volume_publisher.subscribe(tx);
|
||||
}
|
||||
|
||||
/// Configure ce node pour écouter un volume master
|
||||
///
|
||||
/// Le node appliquera à la fois son volume local ET le volume master reçu.
|
||||
pub fn set_master_volume_source(&mut self, rx: mpsc::Receiver<VolumeChangeEvent>) {
|
||||
self.master_volume_rx = Some(rx);
|
||||
}
|
||||
|
||||
/// Retourne un handle pour contrôler le volume depuis un autre contexte
|
||||
pub fn get_handle(&self) -> VolumeHandle {
|
||||
VolumeHandle {
|
||||
volume: self.volume.clone(),
|
||||
node_id: self.node_id.clone(),
|
||||
publisher: Arc::new(RwLock::new(self.volume_publisher.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Démarre la boucle de traitement du VolumeNode
|
||||
pub async fn run(mut self) -> Result<(), AudioError> {
|
||||
let mut master_volume = 1.0f32;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// Recevoir les chunks audio
|
||||
chunk_opt = self.rx.recv() => {
|
||||
match chunk_opt {
|
||||
Some(chunk) => {
|
||||
let local_volume = *self.volume.read().await;
|
||||
let total_volume = (local_volume * master_volume).max(0.0);
|
||||
|
||||
// Créer un nouveau chunk avec le gain modifié (conversion vers dB)
|
||||
let modified_chunk =
|
||||
chunk.with_modified_gain_linear(total_volume as f64);
|
||||
|
||||
// Envoyer aux subscribers
|
||||
self.subscribers.push(modified_chunk).await?;
|
||||
}
|
||||
None => {
|
||||
// Channel fermé, terminer
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recevoir les mises à jour du volume master (si configuré)
|
||||
master_event_opt = async {
|
||||
if let Some(ref mut rx) = self.master_volume_rx {
|
||||
rx.recv().await
|
||||
} else {
|
||||
// Bloquer indéfiniment si pas de master
|
||||
std::future::pending().await
|
||||
}
|
||||
} => {
|
||||
if let Some(event) = master_event_opt {
|
||||
master_volume = event.volume;
|
||||
|
||||
// Optionnel : re-publier l'événement combiné
|
||||
let local_volume = *self.volume.read().await;
|
||||
let combined_event = VolumeChangeEvent {
|
||||
volume: local_volume * master_volume,
|
||||
source_node_id: self.node_id.clone(),
|
||||
};
|
||||
self.volume_publisher.publish(combined_event).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle pour contrôler un VolumeNode depuis un autre contexte
|
||||
///
|
||||
/// Ce handle permet de modifier le volume et de notifier les subscribers
|
||||
/// sans avoir accès direct au node.
|
||||
#[derive(Clone)]
|
||||
pub struct VolumeHandle {
|
||||
volume: Arc<RwLock<f32>>,
|
||||
node_id: String,
|
||||
publisher: Arc<RwLock<EventPublisher<VolumeChangeEvent>>>,
|
||||
}
|
||||
|
||||
impl VolumeHandle {
|
||||
/// Modifie le volume
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `new_volume` - Nouveau volume (0.0 à 1.0)
|
||||
pub async fn set_volume(&self, new_volume: f32) {
|
||||
let clamped = new_volume.clamp(0.0, 1.0);
|
||||
*self.volume.write().await = clamped;
|
||||
|
||||
// Publier l'événement de changement
|
||||
let event = VolumeChangeEvent {
|
||||
volume: clamped,
|
||||
source_node_id: self.node_id.clone(),
|
||||
};
|
||||
|
||||
self.publisher.read().await.publish(event).await;
|
||||
}
|
||||
|
||||
/// Obtient le volume courant
|
||||
pub async fn get_volume(&self) -> f32 {
|
||||
*self.volume.read().await
|
||||
}
|
||||
|
||||
/// Augmente le volume de manière relative
|
||||
pub async fn adjust_volume(&self, delta: f32) {
|
||||
let current = *self.volume.read().await;
|
||||
self.set_volume(current + delta).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// HardwareVolumeNode - Contrôle matériel du volume
|
||||
///
|
||||
/// Ce node simule un contrôle hardware du volume. Dans une implémentation réelle,
|
||||
/// il communiquerait avec le driver audio pour ajuster le volume matériel.
|
||||
///
|
||||
/// Pour cette version, il agit de manière similaire à `VolumeNode` mais pourrait
|
||||
/// être étendu pour utiliser des APIs système spécifiques.
|
||||
pub struct HardwareVolumeNode {
|
||||
inner: VolumeNode,
|
||||
}
|
||||
|
||||
impl HardwareVolumeNode {
|
||||
/// Crée un nouveau HardwareVolumeNode
|
||||
pub fn new(
|
||||
node_id: String,
|
||||
initial_volume: f32,
|
||||
channel_size: usize,
|
||||
) -> (Self, mpsc::Sender<Arc<AudioChunk>>) {
|
||||
let (inner, tx) = VolumeNode::new(node_id, initial_volume, channel_size);
|
||||
|
||||
(Self { inner }, tx)
|
||||
}
|
||||
|
||||
/// Ajoute un subscriber
|
||||
pub fn add_subscriber(&mut self, tx: mpsc::Sender<Arc<AudioChunk>>) {
|
||||
self.inner.add_subscriber(tx);
|
||||
}
|
||||
|
||||
/// Obtient un handle pour contrôler le volume
|
||||
pub fn get_handle(&self) -> VolumeHandle {
|
||||
self.inner.get_handle()
|
||||
}
|
||||
|
||||
/// Démarre la boucle de traitement
|
||||
pub async fn run(self) -> Result<(), AudioError> {
|
||||
// Dans une vraie implémentation, on communiquerait avec le hardware ici
|
||||
// Pour l'instant, délègue au VolumeNode standard
|
||||
self.inner.run().await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::BitDepth;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_volume_node_basic() {
|
||||
let (mut node, tx) = VolumeNode::new("test".to_string(), 0.5, 10);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx);
|
||||
|
||||
let handle = tokio::spawn(async move { node.run().await });
|
||||
|
||||
// Envoyer un chunk avec gain 1.0
|
||||
let chunk =
|
||||
AudioChunk::from_channels_f32(0, vec![1.0; 100], vec![1.0; 100], 48000, BitDepth::B24);
|
||||
tx.send(chunk).await.unwrap();
|
||||
|
||||
// Recevoir le chunk modifié
|
||||
let modified = out_rx.recv().await.unwrap();
|
||||
assert!((modified.gain_linear() - 0.5).abs() < 1e-6);
|
||||
|
||||
drop(tx);
|
||||
handle.await.unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_volume_handle() {
|
||||
let (node, tx) = VolumeNode::new("test".to_string(), 1.0, 10);
|
||||
let handle = node.get_handle();
|
||||
|
||||
tokio::spawn(async move { node.run().await });
|
||||
|
||||
// Modifier le volume via le handle
|
||||
handle.set_volume(0.3).await;
|
||||
|
||||
let volume = handle.get_volume().await;
|
||||
assert!((volume - 0.3).abs() < f32::EPSILON);
|
||||
|
||||
drop(tx);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_volume_events() {
|
||||
let (mut node, tx) = VolumeNode::new("test".to_string(), 1.0, 10);
|
||||
let (event_tx, mut event_rx) = mpsc::channel(10);
|
||||
|
||||
node.subscribe_volume_events(event_tx);
|
||||
let handle = node.get_handle();
|
||||
|
||||
tokio::spawn(async move { node.run().await });
|
||||
|
||||
// Changer le volume
|
||||
handle.set_volume(0.7).await;
|
||||
|
||||
// Vérifier l'événement
|
||||
let event = event_rx.recv().await.unwrap();
|
||||
assert!((event.volume - 0.7).abs() < f32::EPSILON);
|
||||
assert_eq!(event.source_node_id, "test");
|
||||
|
||||
drop(tx);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_master_slave_volume() {
|
||||
// Créer le master
|
||||
let (mut master, master_tx) = VolumeNode::new("master".to_string(), 1.0, 10);
|
||||
let (master_event_tx, master_event_rx) = mpsc::channel(10);
|
||||
master.subscribe_volume_events(master_event_tx);
|
||||
let master_handle = master.get_handle();
|
||||
|
||||
// Créer le slave
|
||||
let (mut slave, slave_tx) = VolumeNode::new("slave".to_string(), 0.8, 10);
|
||||
slave.set_master_volume_source(master_event_rx);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
slave.add_subscriber(out_tx);
|
||||
|
||||
tokio::spawn(async move { master.run().await });
|
||||
tokio::spawn(async move { slave.run().await });
|
||||
|
||||
// Envoyer un chunk au slave
|
||||
let chunk =
|
||||
AudioChunk::from_channels_f32(0, vec![1.0; 100], vec![1.0; 100], 48000, BitDepth::B24);
|
||||
slave_tx.send(chunk).await.unwrap();
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
||||
|
||||
// Modifier le volume master
|
||||
master_handle.set_volume(0.5).await;
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
||||
|
||||
// Envoyer un autre chunk
|
||||
let chunk2 =
|
||||
AudioChunk::from_channels_f32(1, vec![1.0; 100], vec![1.0; 100], 48000, BitDepth::B24);
|
||||
slave_tx.send(chunk2).await.unwrap();
|
||||
|
||||
// Le deuxième chunk devrait avoir un gain de 0.8 * 0.5 = 0.4 (≈ -7.96 dB)
|
||||
let _first = out_rx.recv().await.unwrap(); // gain ≈ 0.8
|
||||
let second = out_rx.recv().await.unwrap(); // gain ≈ 0.4
|
||||
|
||||
assert!((second.gain_linear() - 0.4).abs() < 0.01);
|
||||
|
||||
drop(master_tx);
|
||||
drop(slave_tx);
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,3 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rust-embed = "8.5.0"
|
||||
|
||||
[dependencies.pmoserver]
|
||||
path = "../pmoserver"
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
pmoserver = ["dep:pmoserver"]
|
||||
|
||||
@@ -1,241 +1,30 @@
|
||||
//! # pmoapp - Application web UPnP pour PMOMusic
|
||||
//!
|
||||
//! Cette crate fournit l'application web frontend pour le contrôle et la visualisation
|
||||
//! des devices UPnP MediaRenderer, intégrée via RustEmbed pour être servie par pmoserver.
|
||||
//!
|
||||
//! ## Vue d'ensemble
|
||||
//!
|
||||
//! `pmoapp` est une application Vue.js 3 moderne avec TypeScript qui offre une interface
|
||||
//! utilisateur pour :
|
||||
//! - Visualiser les logs système en temps réel (Server-Sent Events)
|
||||
//! - Contrôler les devices UPnP MediaRenderer
|
||||
//! - Afficher et formater automatiquement le XML dans les logs
|
||||
//! Cette crate fournit l'application web frontend pour le contrôle UPnP,
|
||||
//! intégrée via RustEmbed pour être servie par pmoserver.
|
||||
//!
|
||||
//! ## Fonctionnalités
|
||||
//!
|
||||
//! ### 📦 Frontend intégré
|
||||
//! - Application web compilée et embarquée dans le binaire Rust
|
||||
//! - Aucun fichier statique externe à gérer en production
|
||||
//! - Intégration via `RustEmbed` pour une distribution simplifiée
|
||||
//!
|
||||
//! ### 🎨 Interface utilisateur
|
||||
//! - **LogView** : Visualisation des logs en temps réel avec filtres par niveau
|
||||
//! - **Auto-scroll** : Défilement automatique des nouveaux logs (désactivable)
|
||||
//! - **Formatage XML** : Détection et coloration syntaxique automatique du XML
|
||||
//! - **Design responsive** : Compatible desktop et mobile
|
||||
//! - **Thème sombre** : Style inspiré de VS Code pour une meilleure lisibilité
|
||||
//!
|
||||
//! ### 🚀 Zero configuration
|
||||
//! - Pas besoin de serveur web séparé pour les assets
|
||||
//! - Les fichiers sont servis directement depuis la mémoire du binaire
|
||||
//! - Configuration automatique du routing Vue Router
|
||||
//!
|
||||
//! ## Architecture
|
||||
//!
|
||||
//! ### Stack technique
|
||||
//!
|
||||
//! - **Frontend** : Vue.js 3 avec Composition API
|
||||
//! - **Langage** : TypeScript
|
||||
//! - **Build** : Vite (rapide, moderne, HMR)
|
||||
//! - **Routing** : Vue Router
|
||||
//! - **Markdown** : Marked.js pour le rendu
|
||||
//! - **Sécurité** : DOMPurify pour la sanitization HTML
|
||||
//!
|
||||
//! ### Structure des fichiers
|
||||
//!
|
||||
//! ```text
|
||||
//! pmoapp/
|
||||
//! ├── Cargo.toml # Dépendances Rust (rust-embed)
|
||||
//! ├── src/
|
||||
//! │ └── lib.rs # Point d'entrée Rust (ce fichier)
|
||||
//! └── webapp/
|
||||
//! ├── src/
|
||||
//! │ ├── main.ts # Point d'entrée Vue.js
|
||||
//! │ ├── App.vue # Composant racine
|
||||
//! │ ├── router/ # Configuration Vue Router
|
||||
//! │ └── components/
|
||||
//! │ ├── LogView.vue # Visualiseur de logs SSE
|
||||
//! │ └── ...
|
||||
//! ├── dist/ # Build output (généré, non versionné)
|
||||
//! ├── package.json # Dépendances npm
|
||||
//! └── vite.config.ts # Configuration Vite
|
||||
//! ```
|
||||
//!
|
||||
//! ## Workflow de build
|
||||
//!
|
||||
//! ### 1. Build de la webapp (Vue.js)
|
||||
//!
|
||||
//! ```bash
|
||||
//! # Installation des dépendances
|
||||
//! cd pmoapp/webapp
|
||||
//! npm install
|
||||
//!
|
||||
//! # Build de production
|
||||
//! npm run build
|
||||
//! # Génère : webapp/dist/index.html, assets/*.js, assets/*.css
|
||||
//! ```
|
||||
//!
|
||||
//! ### 2. Compilation Rust
|
||||
//!
|
||||
//! ```bash
|
||||
//! cargo build
|
||||
//! # RustEmbed inclut automatiquement les fichiers de webapp/dist/
|
||||
//! ```
|
||||
//!
|
||||
//! ### 3. Utilisation avec Makefile
|
||||
//!
|
||||
//! ```bash
|
||||
//! # Build complet (webapp + Rust)
|
||||
//! make build
|
||||
//!
|
||||
//! # Ou juste la webapp
|
||||
//! make webapp
|
||||
//!
|
||||
//! # Clean
|
||||
//! make clean
|
||||
//! ```
|
||||
//! - 📦 **Frontend intégré** : Application web compilée et embarquée dans le binaire
|
||||
//! - 🎨 **Interface de contrôle** : UI pour gérer les devices UPnP MediaRenderer
|
||||
//! - 🚀 **Zero configuration** : Pas besoin de servir des fichiers statiques séparés
|
||||
//!
|
||||
//! ## Utilisation
|
||||
//!
|
||||
//! ### Exemple basique
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! ```rust,no_run
|
||||
//! use pmoapp::Webapp;
|
||||
//! use pmoserver::ServerBuilder;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let mut server = ServerBuilder::new("MyApp", "http://localhost", 8080)
|
||||
//! .build();
|
||||
//!
|
||||
//! // Ajouter la webapp comme Single Page Application
|
||||
//! server.add_spa::<Webapp>("/app").await;
|
||||
//!
|
||||
//! // Ajouter une redirection de la racine vers /app
|
||||
//! server.add_redirect("/", "/app").await;
|
||||
//!
|
||||
//! server.start().await;
|
||||
//! server.wait().await;
|
||||
//! }
|
||||
//! # async fn example() {
|
||||
//! let mut server = ServerBuilder::new("MyApp").build();
|
||||
//! server.add_spa::<Webapp>("/app").await;
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Exemple avec logs SSE
|
||||
//! ## Structure
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use pmoapp::Webapp;
|
||||
//! use pmoserver::{ServerBuilder, logs::{LogState, SseLayer}};
|
||||
//! use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! // Configuration des logs avec SSE
|
||||
//! let log_state = LogState::new(1000); // Buffer de 1000 logs
|
||||
//! tracing_subscriber::registry()
|
||||
//! .with(tracing_subscriber::fmt::layer())
|
||||
//! .with(SseLayer::new(log_state.clone()))
|
||||
//! .init();
|
||||
//!
|
||||
//! let mut server = ServerBuilder::new("MyApp", "http://localhost", 8080).build();
|
||||
//!
|
||||
//! // Endpoints SSE pour les logs
|
||||
//! server.add_handler_with_state("/log-sse", pmoserver::logs::log_sse, log_state.clone()).await;
|
||||
//! server.add_handler_with_state("/log-dump", pmoserver::logs::log_dump, log_state).await;
|
||||
//!
|
||||
//! // Webapp (consommera les logs via /log-sse)
|
||||
//! server.add_spa::<Webapp>("/app").await;
|
||||
//! server.add_redirect("/", "/app").await;
|
||||
//!
|
||||
//! server.start().await;
|
||||
//! server.wait().await;
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Développement
|
||||
//!
|
||||
//! ### Mode développement Vue.js
|
||||
//!
|
||||
//! Pour développer la webapp avec Hot Module Replacement :
|
||||
//!
|
||||
//! ```bash
|
||||
//! cd pmoapp/webapp
|
||||
//! npm run dev
|
||||
//! # Serveur de dev sur http://localhost:5173
|
||||
//! ```
|
||||
//!
|
||||
//! ### Rebuild après modifications
|
||||
//!
|
||||
//! Après avoir modifié le code Vue.js :
|
||||
//!
|
||||
//! ```bash
|
||||
//! # Rebuild webapp + recompile Rust
|
||||
//! make build
|
||||
//!
|
||||
//! # Ou séparément
|
||||
//! make webapp # Build Vue.js seulement
|
||||
//! cargo build # Recompile Rust (intègre le nouveau dist/)
|
||||
//! ```
|
||||
//!
|
||||
//! ## Composants Vue.js
|
||||
//!
|
||||
//! ### LogView
|
||||
//!
|
||||
//! Composant principal pour la visualisation des logs :
|
||||
//!
|
||||
//! - **Connexion SSE** : Stream temps réel via EventSource
|
||||
//! - **Filtrage** : Par niveau (TRACE, DEBUG, INFO, WARN, ERROR)
|
||||
//! - **Auto-scroll** : Activable/désactivable
|
||||
//! - **Formatage** : Markdown + détection XML automatique
|
||||
//! - **Buffer** : Limite à 1000 logs en mémoire
|
||||
//! - **Déduplication** : Évite les logs en double
|
||||
//!
|
||||
//! ### Formatage XML
|
||||
//!
|
||||
//! Le composant LogView détecte automatiquement le XML dans les messages :
|
||||
//!
|
||||
//! ```text
|
||||
//! Input: "INFO: <?xml version=\"1.0\"?><scpd>...</scpd>"
|
||||
//! Output: Bloc de code avec coloration syntaxique XML
|
||||
//! ```
|
||||
//!
|
||||
//! - Détection via regex : `<?xml` ou balises courantes (`<scpd>`, `<service>`, etc.)
|
||||
//! - Conversion en bloc markdown : ` ```xml ... ``` `
|
||||
//! - Rendu avec coloration et scrollbar pour le XML long
|
||||
//!
|
||||
//! ## Intégration avec pmoupnp
|
||||
//!
|
||||
//! La webapp communique avec les devices UPnP via les endpoints HTTP fournis par
|
||||
//! `pmoserver` et `pmoupnp` :
|
||||
//!
|
||||
//! - `/log-sse` : Stream de logs (Server-Sent Events)
|
||||
//! - `/log-dump` : Historique des logs
|
||||
//! - `/device/*/description.xml` : Descripteurs UPnP
|
||||
//! - `/service/*/control` : Endpoints de contrôle SOAP
|
||||
//! - `/service/*/event` : Souscription aux événements UPnP
|
||||
//!
|
||||
//! ## Notes de déploiement
|
||||
//!
|
||||
//! ### Taille du binaire
|
||||
//!
|
||||
//! La webapp ajoutera ~150KB au binaire (compressé avec gzip par RustEmbed).
|
||||
//!
|
||||
//! ### Cache du navigateur
|
||||
//!
|
||||
//! Les assets sont servis avec des hashes dans les noms de fichiers
|
||||
//! (`index-BBZcSinC.js`) pour un cache busting automatique.
|
||||
//!
|
||||
//! ### Compatibilité navigateurs
|
||||
//!
|
||||
//! - Chrome/Edge : ✅ Moderne
|
||||
//! - Firefox : ✅ Moderne
|
||||
//! - Safari : ✅ iOS 13+
|
||||
//! - IE11 : ❌ Non supporté (utilise ES modules)
|
||||
//!
|
||||
//! ## Voir aussi
|
||||
//!
|
||||
//! - [`pmoserver`] : Serveur HTTP Axum pour servir la webapp
|
||||
//! - [`pmoupnp`] : Bibliothèque UPnP MediaRenderer
|
||||
//! - [Vue.js Documentation](https://vuejs.org/)
|
||||
//! - [Vite Documentation](https://vitejs.dev/)
|
||||
//! La webapp est construite avec Vite et Vue.js, et les fichiers statiques
|
||||
//! sont embarqués dans le binaire au moment de la compilation via `RustEmbed`.
|
||||
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
@@ -246,73 +35,17 @@ use rust_embed::RustEmbed;
|
||||
///
|
||||
/// ## Exemple
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use pmoapp::{Webapp, WebAppExt};
|
||||
/// ```rust,no_run
|
||||
/// use pmoapp::Webapp;
|
||||
/// use pmoserver::ServerBuilder;
|
||||
///
|
||||
/// # async fn example() {
|
||||
/// let mut server = ServerBuilder::new("MyApp", "http://localhost", 8080).build();
|
||||
/// let mut server = ServerBuilder::new("MyApp").build();
|
||||
///
|
||||
/// // Ajouter la webapp via le trait WebAppExt
|
||||
/// server.add_webapp::<Webapp>("/app").await;
|
||||
/// // Ajouter la webapp comme SPA sur le chemin /app
|
||||
/// server.add_spa::<Webapp>("/app").await;
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(RustEmbed, Clone)]
|
||||
#[folder = "webapp/dist"]
|
||||
pub struct Webapp;
|
||||
|
||||
/// Trait pour étendre un serveur HTTP avec des fonctionnalités webapp.
|
||||
///
|
||||
/// Ce trait permet à `pmoapp` d'ajouter des méthodes d'extension sur des types
|
||||
/// de serveurs externes (comme `pmoserver::Server`) sans que ces crates dépendent de `pmoapp`.
|
||||
///
|
||||
/// # Architecture
|
||||
///
|
||||
/// Similaire au pattern utilisé par `pmoupnp` pour `UpnpServer`, ce trait permet
|
||||
/// une extension propre et découplée :
|
||||
///
|
||||
/// - `pmoserver` définit un serveur HTTP générique
|
||||
/// - `pmoapp` étend ce serveur avec des méthodes webapp via ce trait
|
||||
/// - Le serveur n'a pas besoin de connaître `pmoapp`
|
||||
///
|
||||
/// # Exemple d'implémentation
|
||||
///
|
||||
/// ```ignore
|
||||
/// impl WebAppExt for pmoserver::Server {
|
||||
/// fn add_webapp<W: RustEmbed>(&mut self, path: &str) -> ... {
|
||||
/// // Délègue à la méthode interne add_spa
|
||||
/// self.add_spa::<W>(path)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait WebAppExt {
|
||||
/// Ajoute une Single Page Application au serveur.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - Le chemin où monter la webapp (ex: "/app")
|
||||
///
|
||||
/// # Type Parameter
|
||||
///
|
||||
/// * `W` - Type RustEmbed contenant les fichiers de la webapp
|
||||
async fn add_webapp<W>(&mut self, path: &str)
|
||||
where
|
||||
W: RustEmbed + Clone + Send + Sync + 'static;
|
||||
|
||||
/// Ajoute une webapp avec une redirection automatique depuis la racine.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - Le chemin où monter la webapp (ex: "/app")
|
||||
///
|
||||
/// # Type Parameter
|
||||
///
|
||||
/// * `W` - Type RustEmbed contenant les fichiers de la webapp
|
||||
async fn add_webapp_with_redirect<W>(&mut self, path: &str)
|
||||
where
|
||||
W: RustEmbed + Clone + Send + Sync + 'static;
|
||||
}
|
||||
|
||||
// Implémentation du trait pour pmoserver::Server (feature-gated)
|
||||
#[cfg(feature = "pmoserver")]
|
||||
mod pmoserver_impl;
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
//! Implémentation du trait WebAppExt pour le serveur pmoserver
|
||||
//!
|
||||
//! Ce module enrichit `pmoserver::Server` avec les fonctionnalités webapp en
|
||||
//! implémentant le trait [`WebAppExt`](crate::WebAppExt). Cette implémentation
|
||||
//! permet d'enregistrer facilement des webapps embarquées sur le serveur.
|
||||
//!
|
||||
//! ## Architecture
|
||||
//!
|
||||
//! `pmoapp` étend `pmoserver::Server` sans que `pmoserver` connaisse `pmoapp`.
|
||||
//! C'est le pattern d'extension : `pmoapp` ajoute des fonctionnalités à un type
|
||||
//! externe via un trait, similaire au pattern utilisé par `pmoupnp` pour `UpnpServer`.
|
||||
//!
|
||||
//! ## Exemple d'utilisation
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use pmoapp::{Webapp, WebAppExt};
|
||||
//! use pmoserver::ServerBuilder;
|
||||
//!
|
||||
//! # async fn example() {
|
||||
//! let mut server = ServerBuilder::new("MyApp", "http://localhost", 8080).build();
|
||||
//!
|
||||
//! // Le trait WebAppExt est automatiquement disponible
|
||||
//! server.add_webapp::<Webapp>("/app").await;
|
||||
//!
|
||||
//! // Ou avec redirection
|
||||
//! server.add_webapp_with_redirect::<Webapp>("/app").await;
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use crate::WebAppExt;
|
||||
use pmoserver::Server;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
impl WebAppExt for Server {
|
||||
async fn add_webapp<W>(&mut self, path: &str)
|
||||
where
|
||||
W: RustEmbed + Clone + Send + Sync + 'static,
|
||||
{
|
||||
let path = path.to_string();
|
||||
|
||||
self.add_spa::<W>(&path).await;
|
||||
}
|
||||
|
||||
async fn add_webapp_with_redirect<W>(&mut self, path: &str)
|
||||
where
|
||||
W: RustEmbed + Clone + Send + Sync + 'static,
|
||||
{
|
||||
let path = path.to_string();
|
||||
|
||||
self.add_spa::<W>(&path).await;
|
||||
self.add_redirect("/", &path).await;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
1
pmoapp/webapp/dist/assets/index-C_vitQ9d.css
vendored
Normal file
1
pmoapp/webapp/dist/assets/index-C_vitQ9d.css
vendored
Normal file
File diff suppressed because one or more lines are too long
81
pmoapp/webapp/dist/assets/index-dpjDzo83.js
vendored
Normal file
81
pmoapp/webapp/dist/assets/index-dpjDzo83.js
vendored
Normal file
File diff suppressed because one or more lines are too long
14
pmoapp/webapp/dist/index.html
vendored
Normal file
14
pmoapp/webapp/dist/index.html
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/app/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>webapp</title>
|
||||
<script type="module" crossorigin src="/app/assets/index-dpjDzo83.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/app/assets/index-C_vitQ9d.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
1
pmoapp/webapp/dist/vite.svg
vendored
Normal file
1
pmoapp/webapp/dist/vite.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
pmoapp/webapp/node_modules/.bin/esbuild
generated
vendored
Symbolic link
1
pmoapp/webapp/node_modules/.bin/esbuild
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../esbuild/bin/esbuild
|
||||
1
pmoapp/webapp/node_modules/.bin/marked
generated
vendored
Symbolic link
1
pmoapp/webapp/node_modules/.bin/marked
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../marked/bin/marked.js
|
||||
1
pmoapp/webapp/node_modules/.bin/nanoid
generated
vendored
Symbolic link
1
pmoapp/webapp/node_modules/.bin/nanoid
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../nanoid/bin/nanoid.cjs
|
||||
1
pmoapp/webapp/node_modules/.bin/parser
generated
vendored
Symbolic link
1
pmoapp/webapp/node_modules/.bin/parser
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../@babel/parser/bin/babel-parser.js
|
||||
1
pmoapp/webapp/node_modules/.bin/rollup
generated
vendored
Symbolic link
1
pmoapp/webapp/node_modules/.bin/rollup
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../rollup/dist/bin/rollup
|
||||
1
pmoapp/webapp/node_modules/.bin/tsc
generated
vendored
Symbolic link
1
pmoapp/webapp/node_modules/.bin/tsc
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../typescript/bin/tsc
|
||||
1
pmoapp/webapp/node_modules/.bin/tsserver
generated
vendored
Symbolic link
1
pmoapp/webapp/node_modules/.bin/tsserver
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../typescript/bin/tsserver
|
||||
1
pmoapp/webapp/node_modules/.bin/vite
generated
vendored
Symbolic link
1
pmoapp/webapp/node_modules/.bin/vite
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../vite/bin/vite.js
|
||||
1
pmoapp/webapp/node_modules/.bin/vue-tsc
generated
vendored
Symbolic link
1
pmoapp/webapp/node_modules/.bin/vue-tsc
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../vue-tsc/bin/vue-tsc.js
|
||||
1514
pmoapp/webapp/node_modules/.package-lock.json
generated
vendored
Normal file
1514
pmoapp/webapp/node_modules/.package-lock.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
pmoapp/webapp/node_modules/.tmp/tsconfig.app.tsbuildinfo
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/.tmp/tsconfig.app.tsbuildinfo
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"root":["../../src/main.ts","../../src/shims-vue.d.ts","../../src/router/index.ts","../../src/app.vue","../../src/components/helloworld.vue","../../src/components/logview.vue"],"version":"5.8.3"}
|
||||
1
pmoapp/webapp/node_modules/.tmp/tsconfig.node.tsbuildinfo
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/.tmp/tsconfig.node.tsbuildinfo
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"root":["../../vite.config.ts"],"version":"5.8.3"}
|
||||
137
pmoapp/webapp/node_modules/.vue-global-types/vue_3.5_0.d.ts
generated
vendored
Normal file
137
pmoapp/webapp/node_modules/.vue-global-types/vue_3.5_0.d.ts
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
// @ts-nocheck
|
||||
export {};
|
||||
|
||||
; declare global {
|
||||
const __VLS_directiveBindingRestFields: { instance: null, oldValue: null, modifiers: any, dir: any };
|
||||
const __VLS_unref: typeof import('vue').unref;
|
||||
const __VLS_placeholder: any;
|
||||
|
||||
type __VLS_NativeElements = __VLS_SpreadMerge<SVGElementTagNameMap, HTMLElementTagNameMap>;
|
||||
type __VLS_IntrinsicElements = import('vue/jsx-runtime').JSX.IntrinsicElements;
|
||||
type __VLS_Element = import('vue/jsx-runtime').JSX.Element;
|
||||
type __VLS_GlobalComponents = import('vue').GlobalComponents;
|
||||
type __VLS_GlobalDirectives = import('vue').GlobalDirectives;
|
||||
type __VLS_IsAny<T> = 0 extends 1 & T ? true : false;
|
||||
type __VLS_PickNotAny<A, B> = __VLS_IsAny<A> extends true ? B : A;
|
||||
type __VLS_SpreadMerge<A, B> = Omit<A, keyof B> & B;
|
||||
type __VLS_WithComponent<N0 extends string, LocalComponents, Self, N1 extends string, N2 extends string, N3 extends string> =
|
||||
N1 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N1] } :
|
||||
N2 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N2] } :
|
||||
N3 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N3] } :
|
||||
Self extends object ? { [K in N0]: Self } :
|
||||
N1 extends keyof __VLS_GlobalComponents ? { [K in N0]: __VLS_GlobalComponents[N1] } :
|
||||
N2 extends keyof __VLS_GlobalComponents ? { [K in N0]: __VLS_GlobalComponents[N2] } :
|
||||
N3 extends keyof __VLS_GlobalComponents ? { [K in N0]: __VLS_GlobalComponents[N3] } :
|
||||
{};
|
||||
type __VLS_FunctionalComponentCtx<T, K> = __VLS_PickNotAny<'__ctx' extends keyof __VLS_PickNotAny<K, {}>
|
||||
? K extends { __ctx?: infer Ctx } ? NonNullable<Ctx> : never : any
|
||||
, T extends (props: any, ctx: infer Ctx) => any ? Ctx : any
|
||||
>;
|
||||
type __VLS_FunctionalComponentProps<T, K> = '__ctx' extends keyof __VLS_PickNotAny<K, {}>
|
||||
? K extends { __ctx?: { props?: infer P } } ? NonNullable<P> : never
|
||||
: T extends (props: infer P, ...args: any) => any ? P
|
||||
: {};
|
||||
type __VLS_FunctionalComponent<T> = (props: (T extends { $props: infer Props } ? Props : {}) & Record<string, unknown>, ctx?: any) => __VLS_Element & {
|
||||
__ctx?: {
|
||||
attrs?: any;
|
||||
slots?: T extends { $slots: infer Slots } ? Slots : Record<string, any>;
|
||||
emit?: T extends { $emit: infer Emit } ? Emit : {};
|
||||
props?: (T extends { $props: infer Props } ? Props : {}) & Record<string, unknown>;
|
||||
expose?: (exposed: T) => void;
|
||||
};
|
||||
};
|
||||
type __VLS_IsFunction<T, K> = K extends keyof T
|
||||
? __VLS_IsAny<T[K]> extends false
|
||||
? unknown extends T[K]
|
||||
? false
|
||||
: true
|
||||
: false
|
||||
: false;
|
||||
type __VLS_NormalizeComponentEvent<
|
||||
Props,
|
||||
Emits,
|
||||
onEvent extends keyof Props,
|
||||
Event extends keyof Emits,
|
||||
CamelizedEvent extends keyof Emits,
|
||||
> = __VLS_IsFunction<Props, onEvent> extends true
|
||||
? Props
|
||||
: __VLS_IsFunction<Emits, Event> extends true
|
||||
? { [K in onEvent]?: Emits[Event] }
|
||||
: __VLS_IsFunction<Emits, CamelizedEvent> extends true
|
||||
? { [K in onEvent]?: Emits[CamelizedEvent] }
|
||||
: Props;
|
||||
// fix https://github.com/vuejs/language-tools/issues/926
|
||||
type __VLS_UnionToIntersection<U> = (U extends unknown ? (arg: U) => unknown : never) extends ((arg: infer P) => unknown) ? P : never;
|
||||
type __VLS_OverloadUnionInner<T, U = unknown> = U & T extends (...args: infer A) => infer R
|
||||
? U extends T
|
||||
? never
|
||||
: __VLS_OverloadUnionInner<T, Pick<T, keyof T> & U & ((...args: A) => R)> | ((...args: A) => R)
|
||||
: never;
|
||||
type __VLS_OverloadUnion<T> = Exclude<
|
||||
__VLS_OverloadUnionInner<(() => never) & T>,
|
||||
T extends () => never ? never : () => never
|
||||
>;
|
||||
type __VLS_ConstructorOverloads<T> = __VLS_OverloadUnion<T> extends infer F
|
||||
? F extends (event: infer E, ...args: infer A) => any
|
||||
? { [K in E & string]: (...args: A) => void; }
|
||||
: never
|
||||
: never;
|
||||
type __VLS_NormalizeEmits<T> = __VLS_PrettifyGlobal<
|
||||
__VLS_UnionToIntersection<
|
||||
__VLS_ConstructorOverloads<T> & {
|
||||
[K in keyof T]: T[K] extends any[] ? { (...args: T[K]): void } : never
|
||||
}
|
||||
>
|
||||
>;
|
||||
type __VLS_EmitsToProps<T> = __VLS_PrettifyGlobal<{
|
||||
[K in string & keyof T as `on${Capitalize<K>}`]?:
|
||||
(...args: T[K] extends (...args: infer P) => any ? P : T[K] extends null ? any[] : never) => any;
|
||||
}>;
|
||||
type __VLS_ResolveEmits<
|
||||
Comp,
|
||||
Emits,
|
||||
TypeEmits = {},
|
||||
NormalizedEmits = __VLS_NormalizeEmits<Emits> extends infer E ? string extends keyof E ? {} : E : never,
|
||||
> = __VLS_SpreadMerge<NormalizedEmits, TypeEmits>;
|
||||
type __VLS_ResolveDirectives<T> = {
|
||||
[K in keyof T & string as `v${Capitalize<K>}`]: T[K];
|
||||
};
|
||||
type __VLS_PrettifyGlobal<T> = { [K in keyof T as K]: T[K]; } & {};
|
||||
type __VLS_WithDefaultsGlobal<P, D> = {
|
||||
[K in keyof P as K extends keyof D ? K : never]-?: P[K];
|
||||
} & {
|
||||
[K in keyof P as K extends keyof D ? never : K]: P[K];
|
||||
};
|
||||
type __VLS_UseTemplateRef<T> = Readonly<import('vue').ShallowRef<T | null>>;
|
||||
type __VLS_ProxyRefs<T> = import('vue').ShallowUnwrapRef<T>;
|
||||
|
||||
function __VLS_getVForSourceType<T extends number | string | any[] | Iterable<any>>(source: T): [
|
||||
item: T extends number ? number
|
||||
: T extends string ? string
|
||||
: T extends any[] ? T[number]
|
||||
: T extends Iterable<infer T1> ? T1
|
||||
: any,
|
||||
index: number,
|
||||
][];
|
||||
function __VLS_getVForSourceType<T>(source: T): [
|
||||
item: T[keyof T],
|
||||
key: keyof T,
|
||||
index: number,
|
||||
][];
|
||||
function __VLS_getSlotParameters<S, D extends S>(slot: S, decl?: D):
|
||||
D extends (...args: infer P) => any ? P : any[];
|
||||
function __VLS_asFunctionalDirective<T>(dir: T): T extends import('vue').ObjectDirective
|
||||
? NonNullable<T['created' | 'beforeMount' | 'mounted' | 'beforeUpdate' | 'updated' | 'beforeUnmount' | 'unmounted']>
|
||||
: T extends (...args: any) => any
|
||||
? T
|
||||
: (arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown) => void;
|
||||
function __VLS_asFunctionalComponent<T, K = T extends new (...args: any) => any ? InstanceType<T> : unknown>(t: T, instance?: K):
|
||||
T extends new (...args: any) => any ? __VLS_FunctionalComponent<K>
|
||||
: T extends () => any ? (props: {}, ctx?: any) => ReturnType<T>
|
||||
: T extends (...args: any) => any ? T
|
||||
: __VLS_FunctionalComponent<{}>;
|
||||
function __VLS_functionalComponentArgsRest<T extends (...args: any) => any>(t: T): 2 extends Parameters<T>['length'] ? [any] : [];
|
||||
function __VLS_asFunctionalElement<T>(tag: T, endTag?: T): (attrs: T & Record<string, unknown>) => void;
|
||||
function __VLS_asFunctionalSlot<S>(slot: S): S extends () => infer R ? (props: {}) => R : NonNullable<S>;
|
||||
function __VLS_tryAsConstant<const T>(t: T): T;
|
||||
}
|
||||
22
pmoapp/webapp/node_modules/@babel/helper-string-parser/LICENSE
generated
vendored
Normal file
22
pmoapp/webapp/node_modules/@babel/helper-string-parser/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014-present Sebastian McKenzie and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
19
pmoapp/webapp/node_modules/@babel/helper-string-parser/README.md
generated
vendored
Normal file
19
pmoapp/webapp/node_modules/@babel/helper-string-parser/README.md
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# @babel/helper-string-parser
|
||||
|
||||
> A utility package to parse strings
|
||||
|
||||
See our website [@babel/helper-string-parser](https://babeljs.io/docs/babel-helper-string-parser) for more information.
|
||||
|
||||
## Install
|
||||
|
||||
Using npm:
|
||||
|
||||
```sh
|
||||
npm install --save @babel/helper-string-parser
|
||||
```
|
||||
|
||||
or using yarn:
|
||||
|
||||
```sh
|
||||
yarn add @babel/helper-string-parser
|
||||
```
|
||||
295
pmoapp/webapp/node_modules/@babel/helper-string-parser/lib/index.js
generated
vendored
Normal file
295
pmoapp/webapp/node_modules/@babel/helper-string-parser/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.readCodePoint = readCodePoint;
|
||||
exports.readInt = readInt;
|
||||
exports.readStringContents = readStringContents;
|
||||
var _isDigit = function isDigit(code) {
|
||||
return code >= 48 && code <= 57;
|
||||
};
|
||||
const forbiddenNumericSeparatorSiblings = {
|
||||
decBinOct: new Set([46, 66, 69, 79, 95, 98, 101, 111]),
|
||||
hex: new Set([46, 88, 95, 120])
|
||||
};
|
||||
const isAllowedNumericSeparatorSibling = {
|
||||
bin: ch => ch === 48 || ch === 49,
|
||||
oct: ch => ch >= 48 && ch <= 55,
|
||||
dec: ch => ch >= 48 && ch <= 57,
|
||||
hex: ch => ch >= 48 && ch <= 57 || ch >= 65 && ch <= 70 || ch >= 97 && ch <= 102
|
||||
};
|
||||
function readStringContents(type, input, pos, lineStart, curLine, errors) {
|
||||
const initialPos = pos;
|
||||
const initialLineStart = lineStart;
|
||||
const initialCurLine = curLine;
|
||||
let out = "";
|
||||
let firstInvalidLoc = null;
|
||||
let chunkStart = pos;
|
||||
const {
|
||||
length
|
||||
} = input;
|
||||
for (;;) {
|
||||
if (pos >= length) {
|
||||
errors.unterminated(initialPos, initialLineStart, initialCurLine);
|
||||
out += input.slice(chunkStart, pos);
|
||||
break;
|
||||
}
|
||||
const ch = input.charCodeAt(pos);
|
||||
if (isStringEnd(type, ch, input, pos)) {
|
||||
out += input.slice(chunkStart, pos);
|
||||
break;
|
||||
}
|
||||
if (ch === 92) {
|
||||
out += input.slice(chunkStart, pos);
|
||||
const res = readEscapedChar(input, pos, lineStart, curLine, type === "template", errors);
|
||||
if (res.ch === null && !firstInvalidLoc) {
|
||||
firstInvalidLoc = {
|
||||
pos,
|
||||
lineStart,
|
||||
curLine
|
||||
};
|
||||
} else {
|
||||
out += res.ch;
|
||||
}
|
||||
({
|
||||
pos,
|
||||
lineStart,
|
||||
curLine
|
||||
} = res);
|
||||
chunkStart = pos;
|
||||
} else if (ch === 8232 || ch === 8233) {
|
||||
++pos;
|
||||
++curLine;
|
||||
lineStart = pos;
|
||||
} else if (ch === 10 || ch === 13) {
|
||||
if (type === "template") {
|
||||
out += input.slice(chunkStart, pos) + "\n";
|
||||
++pos;
|
||||
if (ch === 13 && input.charCodeAt(pos) === 10) {
|
||||
++pos;
|
||||
}
|
||||
++curLine;
|
||||
chunkStart = lineStart = pos;
|
||||
} else {
|
||||
errors.unterminated(initialPos, initialLineStart, initialCurLine);
|
||||
}
|
||||
} else {
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
return {
|
||||
pos,
|
||||
str: out,
|
||||
firstInvalidLoc,
|
||||
lineStart,
|
||||
curLine,
|
||||
containsInvalid: !!firstInvalidLoc
|
||||
};
|
||||
}
|
||||
function isStringEnd(type, ch, input, pos) {
|
||||
if (type === "template") {
|
||||
return ch === 96 || ch === 36 && input.charCodeAt(pos + 1) === 123;
|
||||
}
|
||||
return ch === (type === "double" ? 34 : 39);
|
||||
}
|
||||
function readEscapedChar(input, pos, lineStart, curLine, inTemplate, errors) {
|
||||
const throwOnInvalid = !inTemplate;
|
||||
pos++;
|
||||
const res = ch => ({
|
||||
pos,
|
||||
ch,
|
||||
lineStart,
|
||||
curLine
|
||||
});
|
||||
const ch = input.charCodeAt(pos++);
|
||||
switch (ch) {
|
||||
case 110:
|
||||
return res("\n");
|
||||
case 114:
|
||||
return res("\r");
|
||||
case 120:
|
||||
{
|
||||
let code;
|
||||
({
|
||||
code,
|
||||
pos
|
||||
} = readHexChar(input, pos, lineStart, curLine, 2, false, throwOnInvalid, errors));
|
||||
return res(code === null ? null : String.fromCharCode(code));
|
||||
}
|
||||
case 117:
|
||||
{
|
||||
let code;
|
||||
({
|
||||
code,
|
||||
pos
|
||||
} = readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors));
|
||||
return res(code === null ? null : String.fromCodePoint(code));
|
||||
}
|
||||
case 116:
|
||||
return res("\t");
|
||||
case 98:
|
||||
return res("\b");
|
||||
case 118:
|
||||
return res("\u000b");
|
||||
case 102:
|
||||
return res("\f");
|
||||
case 13:
|
||||
if (input.charCodeAt(pos) === 10) {
|
||||
++pos;
|
||||
}
|
||||
case 10:
|
||||
lineStart = pos;
|
||||
++curLine;
|
||||
case 8232:
|
||||
case 8233:
|
||||
return res("");
|
||||
case 56:
|
||||
case 57:
|
||||
if (inTemplate) {
|
||||
return res(null);
|
||||
} else {
|
||||
errors.strictNumericEscape(pos - 1, lineStart, curLine);
|
||||
}
|
||||
default:
|
||||
if (ch >= 48 && ch <= 55) {
|
||||
const startPos = pos - 1;
|
||||
const match = /^[0-7]+/.exec(input.slice(startPos, pos + 2));
|
||||
let octalStr = match[0];
|
||||
let octal = parseInt(octalStr, 8);
|
||||
if (octal > 255) {
|
||||
octalStr = octalStr.slice(0, -1);
|
||||
octal = parseInt(octalStr, 8);
|
||||
}
|
||||
pos += octalStr.length - 1;
|
||||
const next = input.charCodeAt(pos);
|
||||
if (octalStr !== "0" || next === 56 || next === 57) {
|
||||
if (inTemplate) {
|
||||
return res(null);
|
||||
} else {
|
||||
errors.strictNumericEscape(startPos, lineStart, curLine);
|
||||
}
|
||||
}
|
||||
return res(String.fromCharCode(octal));
|
||||
}
|
||||
return res(String.fromCharCode(ch));
|
||||
}
|
||||
}
|
||||
function readHexChar(input, pos, lineStart, curLine, len, forceLen, throwOnInvalid, errors) {
|
||||
const initialPos = pos;
|
||||
let n;
|
||||
({
|
||||
n,
|
||||
pos
|
||||
} = readInt(input, pos, lineStart, curLine, 16, len, forceLen, false, errors, !throwOnInvalid));
|
||||
if (n === null) {
|
||||
if (throwOnInvalid) {
|
||||
errors.invalidEscapeSequence(initialPos, lineStart, curLine);
|
||||
} else {
|
||||
pos = initialPos - 1;
|
||||
}
|
||||
}
|
||||
return {
|
||||
code: n,
|
||||
pos
|
||||
};
|
||||
}
|
||||
function readInt(input, pos, lineStart, curLine, radix, len, forceLen, allowNumSeparator, errors, bailOnError) {
|
||||
const start = pos;
|
||||
const forbiddenSiblings = radix === 16 ? forbiddenNumericSeparatorSiblings.hex : forbiddenNumericSeparatorSiblings.decBinOct;
|
||||
const isAllowedSibling = radix === 16 ? isAllowedNumericSeparatorSibling.hex : radix === 10 ? isAllowedNumericSeparatorSibling.dec : radix === 8 ? isAllowedNumericSeparatorSibling.oct : isAllowedNumericSeparatorSibling.bin;
|
||||
let invalid = false;
|
||||
let total = 0;
|
||||
for (let i = 0, e = len == null ? Infinity : len; i < e; ++i) {
|
||||
const code = input.charCodeAt(pos);
|
||||
let val;
|
||||
if (code === 95 && allowNumSeparator !== "bail") {
|
||||
const prev = input.charCodeAt(pos - 1);
|
||||
const next = input.charCodeAt(pos + 1);
|
||||
if (!allowNumSeparator) {
|
||||
if (bailOnError) return {
|
||||
n: null,
|
||||
pos
|
||||
};
|
||||
errors.numericSeparatorInEscapeSequence(pos, lineStart, curLine);
|
||||
} else if (Number.isNaN(next) || !isAllowedSibling(next) || forbiddenSiblings.has(prev) || forbiddenSiblings.has(next)) {
|
||||
if (bailOnError) return {
|
||||
n: null,
|
||||
pos
|
||||
};
|
||||
errors.unexpectedNumericSeparator(pos, lineStart, curLine);
|
||||
}
|
||||
++pos;
|
||||
continue;
|
||||
}
|
||||
if (code >= 97) {
|
||||
val = code - 97 + 10;
|
||||
} else if (code >= 65) {
|
||||
val = code - 65 + 10;
|
||||
} else if (_isDigit(code)) {
|
||||
val = code - 48;
|
||||
} else {
|
||||
val = Infinity;
|
||||
}
|
||||
if (val >= radix) {
|
||||
if (val <= 9 && bailOnError) {
|
||||
return {
|
||||
n: null,
|
||||
pos
|
||||
};
|
||||
} else if (val <= 9 && errors.invalidDigit(pos, lineStart, curLine, radix)) {
|
||||
val = 0;
|
||||
} else if (forceLen) {
|
||||
val = 0;
|
||||
invalid = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
++pos;
|
||||
total = total * radix + val;
|
||||
}
|
||||
if (pos === start || len != null && pos - start !== len || invalid) {
|
||||
return {
|
||||
n: null,
|
||||
pos
|
||||
};
|
||||
}
|
||||
return {
|
||||
n: total,
|
||||
pos
|
||||
};
|
||||
}
|
||||
function readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors) {
|
||||
const ch = input.charCodeAt(pos);
|
||||
let code;
|
||||
if (ch === 123) {
|
||||
++pos;
|
||||
({
|
||||
code,
|
||||
pos
|
||||
} = readHexChar(input, pos, lineStart, curLine, input.indexOf("}", pos) - pos, true, throwOnInvalid, errors));
|
||||
++pos;
|
||||
if (code !== null && code > 0x10ffff) {
|
||||
if (throwOnInvalid) {
|
||||
errors.invalidCodePoint(pos, lineStart, curLine);
|
||||
} else {
|
||||
return {
|
||||
code: null,
|
||||
pos
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
({
|
||||
code,
|
||||
pos
|
||||
} = readHexChar(input, pos, lineStart, curLine, 4, false, throwOnInvalid, errors));
|
||||
}
|
||||
return {
|
||||
code,
|
||||
pos
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/helper-string-parser/lib/index.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/helper-string-parser/lib/index.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
31
pmoapp/webapp/node_modules/@babel/helper-string-parser/package.json
generated
vendored
Normal file
31
pmoapp/webapp/node_modules/@babel/helper-string-parser/package.json
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@babel/helper-string-parser",
|
||||
"version": "7.27.1",
|
||||
"description": "A utility package to parse strings",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/babel/babel.git",
|
||||
"directory": "packages/babel-helper-string-parser"
|
||||
},
|
||||
"homepage": "https://babel.dev/docs/en/next/babel-helper-string-parser",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"main": "./lib/index.js",
|
||||
"devDependencies": {
|
||||
"charcodes": "^0.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"author": "The Babel Team (https://babel.dev/team)",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"type": "commonjs"
|
||||
}
|
||||
22
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/LICENSE
generated
vendored
Normal file
22
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014-present Sebastian McKenzie and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
19
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/README.md
generated
vendored
Normal file
19
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/README.md
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# @babel/helper-validator-identifier
|
||||
|
||||
> Validate identifier/keywords name
|
||||
|
||||
See our website [@babel/helper-validator-identifier](https://babeljs.io/docs/babel-helper-validator-identifier) for more information.
|
||||
|
||||
## Install
|
||||
|
||||
Using npm:
|
||||
|
||||
```sh
|
||||
npm install --save @babel/helper-validator-identifier
|
||||
```
|
||||
|
||||
or using yarn:
|
||||
|
||||
```sh
|
||||
yarn add @babel/helper-validator-identifier
|
||||
```
|
||||
70
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/lib/identifier.js
generated
vendored
Normal file
70
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/lib/identifier.js
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.isIdentifierChar = isIdentifierChar;
|
||||
exports.isIdentifierName = isIdentifierName;
|
||||
exports.isIdentifierStart = isIdentifierStart;
|
||||
let nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0560-\u0588\u05d0-\u05ea\u05ef-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u0860-\u086a\u0870-\u0887\u0889-\u088e\u08a0-\u08c9\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u09fc\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0af9\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58-\u0c5a\u0c5d\u0c60\u0c61\u0c80\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cdd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d04-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d54-\u0d56\u0d5f-\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e86-\u0e8a\u0e8c-\u0ea3\u0ea5\u0ea7-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u1711\u171f-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1878\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4c\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1c80-\u1c8a\u1c90-\u1cba\u1cbd-\u1cbf\u1ce9-\u1cec\u1cee-\u1cf3\u1cf5\u1cf6\u1cfa\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312f\u3131-\u318e\u31a0-\u31bf\u31f0-\u31ff\u3400-\u4dbf\u4e00-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua7cd\ua7d0\ua7d1\ua7d3\ua7d5-\ua7dc\ua7f2-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua8fd\ua8fe\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab69\uab70-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc";
|
||||
let nonASCIIidentifierChars = "\xb7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u07fd\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u0897-\u089f\u08ca-\u08e1\u08e3-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u09fe\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0afa-\u0aff\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b55-\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c04\u0c3c\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0cf3\u0d00-\u0d03\u0d3b\u0d3c\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d81-\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0ebc\u0ec8-\u0ece\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u180f-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1abf-\u1ace\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf4\u1cf7-\u1cf9\u1dc0-\u1dff\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\u30fb\ua620-\ua629\ua66f\ua674-\ua67d\ua69e\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua82c\ua880\ua881\ua8b4-\ua8c5\ua8d0-\ua8d9\ua8e0-\ua8f1\ua8ff-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f\uff65";
|
||||
const nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
|
||||
const nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
|
||||
nonASCIIidentifierStartChars = nonASCIIidentifierChars = null;
|
||||
const astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 14, 29, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 19, 35, 5, 35, 5, 39, 9, 51, 13, 10, 2, 14, 2, 6, 2, 1, 2, 10, 2, 14, 2, 6, 2, 1, 4, 51, 13, 310, 10, 21, 11, 7, 25, 5, 2, 41, 2, 8, 70, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 66, 18, 2, 1, 11, 21, 11, 25, 71, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 28, 43, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 56, 50, 14, 50, 14, 35, 39, 27, 10, 22, 251, 41, 7, 1, 17, 2, 60, 28, 11, 0, 9, 21, 43, 17, 47, 20, 28, 22, 13, 52, 58, 1, 3, 0, 14, 44, 33, 24, 27, 35, 30, 0, 3, 0, 9, 34, 4, 0, 13, 47, 15, 3, 22, 0, 2, 0, 36, 17, 2, 24, 20, 1, 64, 6, 2, 0, 2, 3, 2, 14, 2, 9, 8, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 19, 0, 13, 4, 31, 9, 2, 0, 3, 0, 2, 37, 2, 0, 26, 0, 2, 0, 45, 52, 19, 3, 21, 2, 31, 47, 21, 1, 2, 0, 185, 46, 42, 3, 37, 47, 21, 0, 60, 42, 14, 0, 72, 26, 38, 6, 186, 43, 117, 63, 32, 7, 3, 0, 3, 7, 2, 1, 2, 23, 16, 0, 2, 0, 95, 7, 3, 38, 17, 0, 2, 0, 29, 0, 11, 39, 8, 0, 22, 0, 12, 45, 20, 0, 19, 72, 200, 32, 32, 8, 2, 36, 18, 0, 50, 29, 113, 6, 2, 1, 2, 37, 22, 0, 26, 5, 2, 1, 2, 31, 15, 0, 328, 18, 16, 0, 2, 12, 2, 33, 125, 0, 80, 921, 103, 110, 18, 195, 2637, 96, 16, 1071, 18, 5, 26, 3994, 6, 582, 6842, 29, 1763, 568, 8, 30, 18, 78, 18, 29, 19, 47, 17, 3, 32, 20, 6, 18, 433, 44, 212, 63, 129, 74, 6, 0, 67, 12, 65, 1, 2, 0, 29, 6135, 9, 1237, 42, 9, 8936, 3, 2, 6, 2, 1, 2, 290, 16, 0, 30, 2, 3, 0, 15, 3, 9, 395, 2309, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 1845, 30, 7, 5, 262, 61, 147, 44, 11, 6, 17, 0, 322, 29, 19, 43, 485, 27, 229, 29, 3, 0, 496, 6, 2, 3, 2, 1, 2, 14, 2, 196, 60, 67, 8, 0, 1205, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42719, 33, 4153, 7, 221, 3, 5761, 15, 7472, 16, 621, 2467, 541, 1507, 4938, 6, 4191];
|
||||
const astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 574, 3, 9, 9, 7, 9, 32, 4, 318, 1, 80, 3, 71, 10, 50, 3, 123, 2, 54, 14, 32, 10, 3, 1, 11, 3, 46, 10, 8, 0, 46, 9, 7, 2, 37, 13, 2, 9, 6, 1, 45, 0, 13, 2, 49, 13, 9, 3, 2, 11, 83, 11, 7, 0, 3, 0, 158, 11, 6, 9, 7, 3, 56, 1, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 68, 8, 2, 0, 3, 0, 2, 3, 2, 4, 2, 0, 15, 1, 83, 17, 10, 9, 5, 0, 82, 19, 13, 9, 214, 6, 3, 8, 28, 1, 83, 16, 16, 9, 82, 12, 9, 9, 7, 19, 58, 14, 5, 9, 243, 14, 166, 9, 71, 5, 2, 1, 3, 3, 2, 0, 2, 1, 13, 9, 120, 6, 3, 6, 4, 0, 29, 9, 41, 6, 2, 3, 9, 0, 10, 10, 47, 15, 343, 9, 54, 7, 2, 7, 17, 9, 57, 21, 2, 13, 123, 5, 4, 0, 2, 1, 2, 6, 2, 0, 9, 9, 49, 4, 2, 1, 2, 4, 9, 9, 330, 3, 10, 1, 2, 0, 49, 6, 4, 4, 14, 10, 5350, 0, 7, 14, 11465, 27, 2343, 9, 87, 9, 39, 4, 60, 6, 26, 9, 535, 9, 470, 0, 2, 54, 8, 3, 82, 0, 12, 1, 19628, 1, 4178, 9, 519, 45, 3, 22, 543, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 513, 54, 5, 49, 9, 0, 15, 0, 23, 4, 2, 14, 1361, 6, 2, 16, 3, 6, 2, 1, 2, 4, 101, 0, 161, 6, 10, 9, 357, 0, 62, 13, 499, 13, 245, 1, 2, 9, 726, 6, 110, 6, 6, 9, 4759, 9, 787719, 239];
|
||||
function isInAstralSet(code, set) {
|
||||
let pos = 0x10000;
|
||||
for (let i = 0, length = set.length; i < length; i += 2) {
|
||||
pos += set[i];
|
||||
if (pos > code) return false;
|
||||
pos += set[i + 1];
|
||||
if (pos >= code) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function isIdentifierStart(code) {
|
||||
if (code < 65) return code === 36;
|
||||
if (code <= 90) return true;
|
||||
if (code < 97) return code === 95;
|
||||
if (code <= 122) return true;
|
||||
if (code <= 0xffff) {
|
||||
return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
|
||||
}
|
||||
return isInAstralSet(code, astralIdentifierStartCodes);
|
||||
}
|
||||
function isIdentifierChar(code) {
|
||||
if (code < 48) return code === 36;
|
||||
if (code < 58) return true;
|
||||
if (code < 65) return false;
|
||||
if (code <= 90) return true;
|
||||
if (code < 97) return code === 95;
|
||||
if (code <= 122) return true;
|
||||
if (code <= 0xffff) {
|
||||
return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
|
||||
}
|
||||
return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes);
|
||||
}
|
||||
function isIdentifierName(name) {
|
||||
let isFirst = true;
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
let cp = name.charCodeAt(i);
|
||||
if ((cp & 0xfc00) === 0xd800 && i + 1 < name.length) {
|
||||
const trail = name.charCodeAt(++i);
|
||||
if ((trail & 0xfc00) === 0xdc00) {
|
||||
cp = 0x10000 + ((cp & 0x3ff) << 10) + (trail & 0x3ff);
|
||||
}
|
||||
}
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
if (!isIdentifierStart(cp)) {
|
||||
return false;
|
||||
}
|
||||
} else if (!isIdentifierChar(cp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !isFirst;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=identifier.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/lib/identifier.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/lib/identifier.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
57
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/lib/index.js
generated
vendored
Normal file
57
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
Object.defineProperty(exports, "isIdentifierChar", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _identifier.isIdentifierChar;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(exports, "isIdentifierName", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _identifier.isIdentifierName;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(exports, "isIdentifierStart", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _identifier.isIdentifierStart;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(exports, "isKeyword", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _keyword.isKeyword;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(exports, "isReservedWord", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _keyword.isReservedWord;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(exports, "isStrictBindOnlyReservedWord", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _keyword.isStrictBindOnlyReservedWord;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(exports, "isStrictBindReservedWord", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _keyword.isStrictBindReservedWord;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(exports, "isStrictReservedWord", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _keyword.isStrictReservedWord;
|
||||
}
|
||||
});
|
||||
var _identifier = require("./identifier.js");
|
||||
var _keyword = require("./keyword.js");
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/lib/index.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/lib/index.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"names":["_identifier","require","_keyword"],"sources":["../src/index.ts"],"sourcesContent":["export {\n isIdentifierName,\n isIdentifierChar,\n isIdentifierStart,\n} from \"./identifier.ts\";\nexport {\n isReservedWord,\n isStrictBindOnlyReservedWord,\n isStrictBindReservedWord,\n isStrictReservedWord,\n isKeyword,\n} from \"./keyword.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,WAAA,GAAAC,OAAA;AAKA,IAAAC,QAAA,GAAAD,OAAA","ignoreList":[]}
|
||||
35
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/lib/keyword.js
generated
vendored
Normal file
35
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/lib/keyword.js
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.isKeyword = isKeyword;
|
||||
exports.isReservedWord = isReservedWord;
|
||||
exports.isStrictBindOnlyReservedWord = isStrictBindOnlyReservedWord;
|
||||
exports.isStrictBindReservedWord = isStrictBindReservedWord;
|
||||
exports.isStrictReservedWord = isStrictReservedWord;
|
||||
const reservedWords = {
|
||||
keyword: ["break", "case", "catch", "continue", "debugger", "default", "do", "else", "finally", "for", "function", "if", "return", "switch", "throw", "try", "var", "const", "while", "with", "new", "this", "super", "class", "extends", "export", "import", "null", "true", "false", "in", "instanceof", "typeof", "void", "delete"],
|
||||
strict: ["implements", "interface", "let", "package", "private", "protected", "public", "static", "yield"],
|
||||
strictBind: ["eval", "arguments"]
|
||||
};
|
||||
const keywords = new Set(reservedWords.keyword);
|
||||
const reservedWordsStrictSet = new Set(reservedWords.strict);
|
||||
const reservedWordsStrictBindSet = new Set(reservedWords.strictBind);
|
||||
function isReservedWord(word, inModule) {
|
||||
return inModule && word === "await" || word === "enum";
|
||||
}
|
||||
function isStrictReservedWord(word, inModule) {
|
||||
return isReservedWord(word, inModule) || reservedWordsStrictSet.has(word);
|
||||
}
|
||||
function isStrictBindOnlyReservedWord(word) {
|
||||
return reservedWordsStrictBindSet.has(word);
|
||||
}
|
||||
function isStrictBindReservedWord(word, inModule) {
|
||||
return isStrictReservedWord(word, inModule) || isStrictBindOnlyReservedWord(word);
|
||||
}
|
||||
function isKeyword(word) {
|
||||
return keywords.has(word);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=keyword.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/lib/keyword.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/lib/keyword.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"names":["reservedWords","keyword","strict","strictBind","keywords","Set","reservedWordsStrictSet","reservedWordsStrictBindSet","isReservedWord","word","inModule","isStrictReservedWord","has","isStrictBindOnlyReservedWord","isStrictBindReservedWord","isKeyword"],"sources":["../src/keyword.ts"],"sourcesContent":["const reservedWords = {\n keyword: [\n \"break\",\n \"case\",\n \"catch\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"do\",\n \"else\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"return\",\n \"switch\",\n \"throw\",\n \"try\",\n \"var\",\n \"const\",\n \"while\",\n \"with\",\n \"new\",\n \"this\",\n \"super\",\n \"class\",\n \"extends\",\n \"export\",\n \"import\",\n \"null\",\n \"true\",\n \"false\",\n \"in\",\n \"instanceof\",\n \"typeof\",\n \"void\",\n \"delete\",\n ],\n strict: [\n \"implements\",\n \"interface\",\n \"let\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n \"static\",\n \"yield\",\n ],\n strictBind: [\"eval\", \"arguments\"],\n};\nconst keywords = new Set(reservedWords.keyword);\nconst reservedWordsStrictSet = new Set(reservedWords.strict);\nconst reservedWordsStrictBindSet = new Set(reservedWords.strictBind);\n\n/**\n * Checks if word is a reserved word in non-strict mode\n */\nexport function isReservedWord(word: string, inModule: boolean): boolean {\n return (inModule && word === \"await\") || word === \"enum\";\n}\n\n/**\n * Checks if word is a reserved word in non-binding strict mode\n *\n * Includes non-strict reserved words\n */\nexport function isStrictReservedWord(word: string, inModule: boolean): boolean {\n return isReservedWord(word, inModule) || reservedWordsStrictSet.has(word);\n}\n\n/**\n * Checks if word is a reserved word in binding strict mode, but it is allowed as\n * a normal identifier.\n */\nexport function isStrictBindOnlyReservedWord(word: string): boolean {\n return reservedWordsStrictBindSet.has(word);\n}\n\n/**\n * Checks if word is a reserved word in binding strict mode\n *\n * Includes non-strict reserved words and non-binding strict reserved words\n */\nexport function isStrictBindReservedWord(\n word: string,\n inModule: boolean,\n): boolean {\n return (\n isStrictReservedWord(word, inModule) || isStrictBindOnlyReservedWord(word)\n );\n}\n\nexport function isKeyword(word: string): boolean {\n return keywords.has(word);\n}\n"],"mappings":";;;;;;;;;;AAAA,MAAMA,aAAa,GAAG;EACpBC,OAAO,EAAE,CACP,OAAO,EACP,MAAM,EACN,OAAO,EACP,UAAU,EACV,UAAU,EACV,SAAS,EACT,IAAI,EACJ,MAAM,EACN,SAAS,EACT,KAAK,EACL,UAAU,EACV,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,MAAM,EACN,KAAK,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,MAAM,EACN,QAAQ,CACT;EACDC,MAAM,EAAE,CACN,YAAY,EACZ,WAAW,EACX,KAAK,EACL,SAAS,EACT,SAAS,EACT,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,OAAO,CACR;EACDC,UAAU,EAAE,CAAC,MAAM,EAAE,WAAW;AAClC,CAAC;AACD,MAAMC,QAAQ,GAAG,IAAIC,GAAG,CAACL,aAAa,CAACC,OAAO,CAAC;AAC/C,MAAMK,sBAAsB,GAAG,IAAID,GAAG,CAACL,aAAa,CAACE,MAAM,CAAC;AAC5D,MAAMK,0BAA0B,GAAG,IAAIF,GAAG,CAACL,aAAa,CAACG,UAAU,CAAC;AAK7D,SAASK,cAAcA,CAACC,IAAY,EAAEC,QAAiB,EAAW;EACvE,OAAQA,QAAQ,IAAID,IAAI,KAAK,OAAO,IAAKA,IAAI,KAAK,MAAM;AAC1D;AAOO,SAASE,oBAAoBA,CAACF,IAAY,EAAEC,QAAiB,EAAW;EAC7E,OAAOF,cAAc,CAACC,IAAI,EAAEC,QAAQ,CAAC,IAAIJ,sBAAsB,CAACM,GAAG,CAACH,IAAI,CAAC;AAC3E;AAMO,SAASI,4BAA4BA,CAACJ,IAAY,EAAW;EAClE,OAAOF,0BAA0B,CAACK,GAAG,CAACH,IAAI,CAAC;AAC7C;AAOO,SAASK,wBAAwBA,CACtCL,IAAY,EACZC,QAAiB,EACR;EACT,OACEC,oBAAoB,CAACF,IAAI,EAAEC,QAAQ,CAAC,IAAIG,4BAA4B,CAACJ,IAAI,CAAC;AAE9E;AAEO,SAASM,SAASA,CAACN,IAAY,EAAW;EAC/C,OAAOL,QAAQ,CAACQ,GAAG,CAACH,IAAI,CAAC;AAC3B","ignoreList":[]}
|
||||
31
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/package.json
generated
vendored
Normal file
31
pmoapp/webapp/node_modules/@babel/helper-validator-identifier/package.json
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@babel/helper-validator-identifier",
|
||||
"version": "7.27.1",
|
||||
"description": "Validate identifier/keywords name",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/babel/babel.git",
|
||||
"directory": "packages/babel-helper-validator-identifier"
|
||||
},
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"main": "./lib/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@unicode/unicode-16.0.0": "^1.0.0",
|
||||
"charcodes": "^0.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"author": "The Babel Team (https://babel.dev/team)",
|
||||
"type": "commonjs"
|
||||
}
|
||||
1073
pmoapp/webapp/node_modules/@babel/parser/CHANGELOG.md
generated
vendored
Normal file
1073
pmoapp/webapp/node_modules/@babel/parser/CHANGELOG.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
19
pmoapp/webapp/node_modules/@babel/parser/LICENSE
generated
vendored
Normal file
19
pmoapp/webapp/node_modules/@babel/parser/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2012-2014 by various contributors (see AUTHORS)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
19
pmoapp/webapp/node_modules/@babel/parser/README.md
generated
vendored
Normal file
19
pmoapp/webapp/node_modules/@babel/parser/README.md
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# @babel/parser
|
||||
|
||||
> A JavaScript parser
|
||||
|
||||
See our website [@babel/parser](https://babeljs.io/docs/babel-parser) for more information or the [issues](https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A%20parser%22+is%3Aopen) associated with this package.
|
||||
|
||||
## Install
|
||||
|
||||
Using npm:
|
||||
|
||||
```sh
|
||||
npm install --save-dev @babel/parser
|
||||
```
|
||||
|
||||
or using yarn:
|
||||
|
||||
```sh
|
||||
yarn add @babel/parser --dev
|
||||
```
|
||||
15
pmoapp/webapp/node_modules/@babel/parser/bin/babel-parser.js
generated
vendored
Executable file
15
pmoapp/webapp/node_modules/@babel/parser/bin/babel-parser.js
generated
vendored
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-var, unicorn/prefer-node-protocol */
|
||||
|
||||
var parser = require("..");
|
||||
var fs = require("fs");
|
||||
|
||||
var filename = process.argv[2];
|
||||
if (!filename) {
|
||||
console.error("no filename specified");
|
||||
} else {
|
||||
var file = fs.readFileSync(filename, "utf8");
|
||||
var ast = parser.parse(file);
|
||||
|
||||
console.log(JSON.stringify(ast, null, " "));
|
||||
}
|
||||
14595
pmoapp/webapp/node_modules/@babel/parser/lib/index.js
generated
vendored
Normal file
14595
pmoapp/webapp/node_modules/@babel/parser/lib/index.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
50
pmoapp/webapp/node_modules/@babel/parser/package.json
generated
vendored
Normal file
50
pmoapp/webapp/node_modules/@babel/parser/package.json
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@babel/parser",
|
||||
"version": "7.28.4",
|
||||
"description": "A JavaScript parser",
|
||||
"author": "The Babel Team (https://babel.dev/team)",
|
||||
"homepage": "https://babel.dev/docs/en/next/babel-parser",
|
||||
"bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A+parser+%28babylon%29%22+is%3Aopen",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keywords": [
|
||||
"babel",
|
||||
"javascript",
|
||||
"parser",
|
||||
"tc39",
|
||||
"ecmascript",
|
||||
"@babel/parser"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/babel/babel.git",
|
||||
"directory": "packages/babel-parser"
|
||||
},
|
||||
"main": "./lib/index.js",
|
||||
"types": "./typings/babel-parser.d.ts",
|
||||
"files": [
|
||||
"bin",
|
||||
"lib",
|
||||
"typings/babel-parser.d.ts",
|
||||
"index.cjs"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
},
|
||||
"# dependencies": "This package doesn't actually have runtime dependencies. @babel/types is only needed for type definitions.",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/helper-check-duplicate-nodes": "^7.27.1",
|
||||
"@babel/helper-fixtures": "^7.28.0",
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.27.1",
|
||||
"charcodes": "^0.2.0"
|
||||
},
|
||||
"bin": "./bin/babel-parser.js",
|
||||
"type": "commonjs"
|
||||
}
|
||||
239
pmoapp/webapp/node_modules/@babel/parser/typings/babel-parser.d.ts
generated
vendored
Normal file
239
pmoapp/webapp/node_modules/@babel/parser/typings/babel-parser.d.ts
generated
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
// This file is auto-generated! Do not modify it directly.
|
||||
// Run `yarn gulp bundle-dts` to re-generate it.
|
||||
/* eslint-disable @typescript-eslint/consistent-type-imports, @typescript-eslint/no-redundant-type-constituents */
|
||||
import { File, Expression } from '@babel/types';
|
||||
|
||||
type BABEL_8_BREAKING = false;
|
||||
type IF_BABEL_7<V> = false extends BABEL_8_BREAKING ? V : never;
|
||||
|
||||
type Plugin$1 =
|
||||
| "asyncDoExpressions"
|
||||
| IF_BABEL_7<"asyncGenerators">
|
||||
| IF_BABEL_7<"bigInt">
|
||||
| IF_BABEL_7<"classPrivateMethods">
|
||||
| IF_BABEL_7<"classPrivateProperties">
|
||||
| IF_BABEL_7<"classProperties">
|
||||
| IF_BABEL_7<"classStaticBlock">
|
||||
| IF_BABEL_7<"decimal">
|
||||
| "decorators-legacy"
|
||||
| "deferredImportEvaluation"
|
||||
| "decoratorAutoAccessors"
|
||||
| "destructuringPrivate"
|
||||
| "deprecatedImportAssert"
|
||||
| "doExpressions"
|
||||
| IF_BABEL_7<"dynamicImport">
|
||||
| IF_BABEL_7<"explicitResourceManagement">
|
||||
| "exportDefaultFrom"
|
||||
| IF_BABEL_7<"exportNamespaceFrom">
|
||||
| "flow"
|
||||
| "flowComments"
|
||||
| "functionBind"
|
||||
| "functionSent"
|
||||
| "importMeta"
|
||||
| "jsx"
|
||||
| IF_BABEL_7<"jsonStrings">
|
||||
| IF_BABEL_7<"logicalAssignment">
|
||||
| IF_BABEL_7<"importAssertions">
|
||||
| IF_BABEL_7<"importReflection">
|
||||
| "moduleBlocks"
|
||||
| IF_BABEL_7<"moduleStringNames">
|
||||
| IF_BABEL_7<"nullishCoalescingOperator">
|
||||
| IF_BABEL_7<"numericSeparator">
|
||||
| IF_BABEL_7<"objectRestSpread">
|
||||
| IF_BABEL_7<"optionalCatchBinding">
|
||||
| IF_BABEL_7<"optionalChaining">
|
||||
| "partialApplication"
|
||||
| "placeholders"
|
||||
| IF_BABEL_7<"privateIn">
|
||||
| IF_BABEL_7<"regexpUnicodeSets">
|
||||
| "sourcePhaseImports"
|
||||
| "throwExpressions"
|
||||
| IF_BABEL_7<"topLevelAwait">
|
||||
| "v8intrinsic"
|
||||
| ParserPluginWithOptions[0];
|
||||
|
||||
type ParserPluginWithOptions =
|
||||
| ["decorators", DecoratorsPluginOptions]
|
||||
| ["discardBinding", { syntaxType: "void" }]
|
||||
| ["estree", { classFeatures?: boolean }]
|
||||
| IF_BABEL_7<["importAttributes", { deprecatedAssertSyntax: boolean }]>
|
||||
| IF_BABEL_7<["moduleAttributes", { version: "may-2020" }]>
|
||||
| ["optionalChainingAssign", { version: "2023-07" }]
|
||||
| ["pipelineOperator", PipelineOperatorPluginOptions]
|
||||
| ["recordAndTuple", RecordAndTuplePluginOptions]
|
||||
| ["flow", FlowPluginOptions]
|
||||
| ["typescript", TypeScriptPluginOptions];
|
||||
|
||||
type PluginConfig = Plugin$1 | ParserPluginWithOptions;
|
||||
|
||||
interface DecoratorsPluginOptions {
|
||||
decoratorsBeforeExport?: boolean;
|
||||
allowCallParenthesized?: boolean;
|
||||
}
|
||||
|
||||
interface PipelineOperatorPluginOptions {
|
||||
proposal: BABEL_8_BREAKING extends false
|
||||
? "minimal" | "fsharp" | "hack" | "smart"
|
||||
: "fsharp" | "hack";
|
||||
topicToken?: "%" | "#" | "@@" | "^^" | "^";
|
||||
}
|
||||
|
||||
interface RecordAndTuplePluginOptions {
|
||||
syntaxType: "bar" | "hash";
|
||||
}
|
||||
|
||||
type FlowPluginOptions = BABEL_8_BREAKING extends true
|
||||
? {
|
||||
all?: boolean;
|
||||
enums?: boolean;
|
||||
}
|
||||
: {
|
||||
all?: boolean;
|
||||
};
|
||||
|
||||
interface TypeScriptPluginOptions {
|
||||
dts?: boolean;
|
||||
disallowAmbiguousJSXLike?: boolean;
|
||||
}
|
||||
|
||||
type Plugin = PluginConfig;
|
||||
|
||||
type SourceType = "script" | "commonjs" | "module" | "unambiguous";
|
||||
interface Options {
|
||||
/**
|
||||
* By default, import and export declarations can only appear at a program's top level.
|
||||
* Setting this option to true allows them anywhere where a statement is allowed.
|
||||
*/
|
||||
allowImportExportEverywhere?: boolean;
|
||||
/**
|
||||
* By default, await use is not allowed outside of an async function.
|
||||
* Set this to true to accept such code.
|
||||
*/
|
||||
allowAwaitOutsideFunction?: boolean;
|
||||
/**
|
||||
* By default, a return statement at the top level raises an error.
|
||||
* Set this to true to accept such code.
|
||||
*/
|
||||
allowReturnOutsideFunction?: boolean;
|
||||
/**
|
||||
* By default, new.target use is not allowed outside of a function or class.
|
||||
* Set this to true to accept such code.
|
||||
*/
|
||||
allowNewTargetOutsideFunction?: boolean;
|
||||
allowSuperOutsideMethod?: boolean;
|
||||
/**
|
||||
* By default, exported identifiers must refer to a declared variable.
|
||||
* Set this to true to allow export statements to reference undeclared variables.
|
||||
*/
|
||||
allowUndeclaredExports?: boolean;
|
||||
/**
|
||||
* By default, yield use is not allowed outside of a generator function.
|
||||
* Set this to true to accept such code.
|
||||
*/
|
||||
allowYieldOutsideFunction?: boolean;
|
||||
/**
|
||||
* By default, Babel parser JavaScript code according to Annex B syntax.
|
||||
* Set this to `false` to disable such behavior.
|
||||
*/
|
||||
annexB?: boolean;
|
||||
/**
|
||||
* By default, Babel attaches comments to adjacent AST nodes.
|
||||
* When this option is set to false, comments are not attached.
|
||||
* It can provide up to 30% performance improvement when the input code has many comments.
|
||||
* @babel/eslint-parser will set it for you.
|
||||
* It is not recommended to use attachComment: false with Babel transform,
|
||||
* as doing so removes all the comments in output code, and renders annotations such as
|
||||
* /* istanbul ignore next *\/ nonfunctional.
|
||||
*/
|
||||
attachComment?: boolean;
|
||||
/**
|
||||
* By default, Babel always throws an error when it finds some invalid code.
|
||||
* When this option is set to true, it will store the parsing error and
|
||||
* try to continue parsing the invalid input file.
|
||||
*/
|
||||
errorRecovery?: boolean;
|
||||
/**
|
||||
* Indicate the mode the code should be parsed in.
|
||||
* Can be one of "script", "commonjs", "module", or "unambiguous". Defaults to "script".
|
||||
* "unambiguous" will make @babel/parser attempt to guess, based on the presence
|
||||
* of ES6 import or export statements.
|
||||
* Files with ES6 imports and exports are considered "module" and are otherwise "script".
|
||||
*
|
||||
* Use "commonjs" to parse code that is intended to be run in a CommonJS environment such as Node.js.
|
||||
*/
|
||||
sourceType?: SourceType;
|
||||
/**
|
||||
* Correlate output AST nodes with their source filename.
|
||||
* Useful when generating code and source maps from the ASTs of multiple input files.
|
||||
*/
|
||||
sourceFilename?: string;
|
||||
/**
|
||||
* By default, all source indexes start from 0.
|
||||
* You can provide a start index to alternatively start with.
|
||||
* Useful for integration with other source tools.
|
||||
*/
|
||||
startIndex?: number;
|
||||
/**
|
||||
* By default, the first line of code parsed is treated as line 1.
|
||||
* You can provide a line number to alternatively start with.
|
||||
* Useful for integration with other source tools.
|
||||
*/
|
||||
startLine?: number;
|
||||
/**
|
||||
* By default, the parsed code is treated as if it starts from line 1, column 0.
|
||||
* You can provide a column number to alternatively start with.
|
||||
* Useful for integration with other source tools.
|
||||
*/
|
||||
startColumn?: number;
|
||||
/**
|
||||
* Array containing the plugins that you want to enable.
|
||||
*/
|
||||
plugins?: Plugin[];
|
||||
/**
|
||||
* Should the parser work in strict mode.
|
||||
* Defaults to true if sourceType === 'module'. Otherwise, false.
|
||||
*/
|
||||
strictMode?: boolean;
|
||||
/**
|
||||
* Adds a ranges property to each node: [node.start, node.end]
|
||||
*/
|
||||
ranges?: boolean;
|
||||
/**
|
||||
* Adds all parsed tokens to a tokens property on the File node.
|
||||
*/
|
||||
tokens?: boolean;
|
||||
/**
|
||||
* By default, the parser adds information about parentheses by setting
|
||||
* `extra.parenthesized` to `true` as needed.
|
||||
* When this option is `true` the parser creates `ParenthesizedExpression`
|
||||
* AST nodes instead of using the `extra` property.
|
||||
*/
|
||||
createParenthesizedExpressions?: boolean;
|
||||
/**
|
||||
* The default is false in Babel 7 and true in Babel 8
|
||||
* Set this to true to parse it as an `ImportExpression` node.
|
||||
* Otherwise `import(foo)` is parsed as `CallExpression(Import, [Identifier(foo)])`.
|
||||
*/
|
||||
createImportExpressions?: boolean;
|
||||
}
|
||||
|
||||
type ParserOptions = Partial<Options>;
|
||||
interface ParseError {
|
||||
code: string;
|
||||
reasonCode: string;
|
||||
}
|
||||
type ParseResult<Result extends File | Expression = File> = Result & {
|
||||
errors: null | ParseError[];
|
||||
};
|
||||
/**
|
||||
* Parse the provided code as an entire ECMAScript program.
|
||||
*/
|
||||
declare function parse(input: string, options?: ParserOptions): ParseResult<File>;
|
||||
declare function parseExpression(input: string, options?: ParserOptions): ParseResult<Expression>;
|
||||
|
||||
declare const tokTypes: {
|
||||
// todo(flow->ts) real token type
|
||||
[name: string]: any;
|
||||
};
|
||||
|
||||
export { DecoratorsPluginOptions, FlowPluginOptions, ParseError, ParseResult, ParserOptions, PluginConfig as ParserPlugin, ParserPluginWithOptions, PipelineOperatorPluginOptions, RecordAndTuplePluginOptions, TypeScriptPluginOptions, parse, parseExpression, tokTypes };
|
||||
22
pmoapp/webapp/node_modules/@babel/types/LICENSE
generated
vendored
Normal file
22
pmoapp/webapp/node_modules/@babel/types/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014-present Sebastian McKenzie and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
19
pmoapp/webapp/node_modules/@babel/types/README.md
generated
vendored
Normal file
19
pmoapp/webapp/node_modules/@babel/types/README.md
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# @babel/types
|
||||
|
||||
> Babel Types is a Lodash-esque utility library for AST nodes
|
||||
|
||||
See our website [@babel/types](https://babeljs.io/docs/babel-types) for more information or the [issues](https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A%20types%22+is%3Aopen) associated with this package.
|
||||
|
||||
## Install
|
||||
|
||||
Using npm:
|
||||
|
||||
```sh
|
||||
npm install --save-dev @babel/types
|
||||
```
|
||||
|
||||
or using yarn:
|
||||
|
||||
```sh
|
||||
yarn add @babel/types --dev
|
||||
```
|
||||
16
pmoapp/webapp/node_modules/@babel/types/lib/asserts/assertNode.js
generated
vendored
Normal file
16
pmoapp/webapp/node_modules/@babel/types/lib/asserts/assertNode.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = assertNode;
|
||||
var _isNode = require("../validators/isNode.js");
|
||||
function assertNode(node) {
|
||||
if (!(0, _isNode.default)(node)) {
|
||||
var _node$type;
|
||||
const type = (_node$type = node == null ? void 0 : node.type) != null ? _node$type : JSON.stringify(node);
|
||||
throw new TypeError(`Not a valid node of type "${type}"`);
|
||||
}
|
||||
}
|
||||
|
||||
//# sourceMappingURL=assertNode.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/asserts/assertNode.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/asserts/assertNode.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"names":["_isNode","require","assertNode","node","isNode","_node$type","type","JSON","stringify","TypeError"],"sources":["../../src/asserts/assertNode.ts"],"sourcesContent":["import isNode from \"../validators/isNode.ts\";\nimport type * as t from \"../index.ts\";\n\nexport default function assertNode(node?: any): asserts node is t.Node {\n if (!isNode(node)) {\n const type = node?.type ?? JSON.stringify(node);\n throw new TypeError(`Not a valid node of type \"${type}\"`);\n }\n}\n"],"mappings":";;;;;;AAAA,IAAAA,OAAA,GAAAC,OAAA;AAGe,SAASC,UAAUA,CAACC,IAAU,EAA0B;EACrE,IAAI,CAAC,IAAAC,eAAM,EAACD,IAAI,CAAC,EAAE;IAAA,IAAAE,UAAA;IACjB,MAAMC,IAAI,IAAAD,UAAA,GAAGF,IAAI,oBAAJA,IAAI,CAAEG,IAAI,YAAAD,UAAA,GAAIE,IAAI,CAACC,SAAS,CAACL,IAAI,CAAC;IAC/C,MAAM,IAAIM,SAAS,CAAC,6BAA6BH,IAAI,GAAG,CAAC;EAC3D;AACF","ignoreList":[]}
|
||||
1251
pmoapp/webapp/node_modules/@babel/types/lib/asserts/generated/index.js
generated
vendored
Normal file
1251
pmoapp/webapp/node_modules/@babel/types/lib/asserts/generated/index.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
pmoapp/webapp/node_modules/@babel/types/lib/asserts/generated/index.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/asserts/generated/index.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
3
pmoapp/webapp/node_modules/@babel/types/lib/ast-types/generated/index.js
generated
vendored
Normal file
3
pmoapp/webapp/node_modules/@babel/types/lib/ast-types/generated/index.js
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/ast-types/generated/index.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/ast-types/generated/index.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
18
pmoapp/webapp/node_modules/@babel/types/lib/builders/flow/createFlowUnionType.js
generated
vendored
Normal file
18
pmoapp/webapp/node_modules/@babel/types/lib/builders/flow/createFlowUnionType.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = createFlowUnionType;
|
||||
var _index = require("../generated/index.js");
|
||||
var _removeTypeDuplicates = require("../../modifications/flow/removeTypeDuplicates.js");
|
||||
function createFlowUnionType(types) {
|
||||
const flattened = (0, _removeTypeDuplicates.default)(types);
|
||||
if (flattened.length === 1) {
|
||||
return flattened[0];
|
||||
} else {
|
||||
return (0, _index.unionTypeAnnotation)(flattened);
|
||||
}
|
||||
}
|
||||
|
||||
//# sourceMappingURL=createFlowUnionType.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/flow/createFlowUnionType.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/flow/createFlowUnionType.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"names":["_index","require","_removeTypeDuplicates","createFlowUnionType","types","flattened","removeTypeDuplicates","length","unionTypeAnnotation"],"sources":["../../../src/builders/flow/createFlowUnionType.ts"],"sourcesContent":["import { unionTypeAnnotation } from \"../generated/index.ts\";\nimport removeTypeDuplicates from \"../../modifications/flow/removeTypeDuplicates.ts\";\nimport type * as t from \"../../index.ts\";\n\n/**\n * Takes an array of `types` and flattens them, removing duplicates and\n * returns a `UnionTypeAnnotation` node containing them.\n */\nexport default function createFlowUnionType<T extends t.FlowType>(\n types: [T] | Array<T>,\n): T | t.UnionTypeAnnotation {\n const flattened = removeTypeDuplicates(types);\n\n if (flattened.length === 1) {\n return flattened[0] as T;\n } else {\n return unionTypeAnnotation(flattened);\n }\n}\n"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,qBAAA,GAAAD,OAAA;AAOe,SAASE,mBAAmBA,CACzCC,KAAqB,EACM;EAC3B,MAAMC,SAAS,GAAG,IAAAC,6BAAoB,EAACF,KAAK,CAAC;EAE7C,IAAIC,SAAS,CAACE,MAAM,KAAK,CAAC,EAAE;IAC1B,OAAOF,SAAS,CAAC,CAAC,CAAC;EACrB,CAAC,MAAM;IACL,OAAO,IAAAG,0BAAmB,EAACH,SAAS,CAAC;EACvC;AACF","ignoreList":[]}
|
||||
31
pmoapp/webapp/node_modules/@babel/types/lib/builders/flow/createTypeAnnotationBasedOnTypeof.js
generated
vendored
Normal file
31
pmoapp/webapp/node_modules/@babel/types/lib/builders/flow/createTypeAnnotationBasedOnTypeof.js
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _index = require("../generated/index.js");
|
||||
var _default = exports.default = createTypeAnnotationBasedOnTypeof;
|
||||
function createTypeAnnotationBasedOnTypeof(type) {
|
||||
switch (type) {
|
||||
case "string":
|
||||
return (0, _index.stringTypeAnnotation)();
|
||||
case "number":
|
||||
return (0, _index.numberTypeAnnotation)();
|
||||
case "undefined":
|
||||
return (0, _index.voidTypeAnnotation)();
|
||||
case "boolean":
|
||||
return (0, _index.booleanTypeAnnotation)();
|
||||
case "function":
|
||||
return (0, _index.genericTypeAnnotation)((0, _index.identifier)("Function"));
|
||||
case "object":
|
||||
return (0, _index.genericTypeAnnotation)((0, _index.identifier)("Object"));
|
||||
case "symbol":
|
||||
return (0, _index.genericTypeAnnotation)((0, _index.identifier)("Symbol"));
|
||||
case "bigint":
|
||||
return (0, _index.anyTypeAnnotation)();
|
||||
}
|
||||
throw new Error("Invalid typeof value: " + type);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=createTypeAnnotationBasedOnTypeof.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/flow/createTypeAnnotationBasedOnTypeof.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/flow/createTypeAnnotationBasedOnTypeof.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"names":["_index","require","_default","exports","default","createTypeAnnotationBasedOnTypeof","type","stringTypeAnnotation","numberTypeAnnotation","voidTypeAnnotation","booleanTypeAnnotation","genericTypeAnnotation","identifier","anyTypeAnnotation","Error"],"sources":["../../../src/builders/flow/createTypeAnnotationBasedOnTypeof.ts"],"sourcesContent":["import {\n anyTypeAnnotation,\n stringTypeAnnotation,\n numberTypeAnnotation,\n voidTypeAnnotation,\n booleanTypeAnnotation,\n genericTypeAnnotation,\n identifier,\n} from \"../generated/index.ts\";\nimport type * as t from \"../../index.ts\";\n\nexport default createTypeAnnotationBasedOnTypeof as {\n (type: \"string\"): t.StringTypeAnnotation;\n (type: \"number\"): t.NumberTypeAnnotation;\n (type: \"undefined\"): t.VoidTypeAnnotation;\n (type: \"boolean\"): t.BooleanTypeAnnotation;\n (type: \"function\"): t.GenericTypeAnnotation;\n (type: \"object\"): t.GenericTypeAnnotation;\n (type: \"symbol\"): t.GenericTypeAnnotation;\n (type: \"bigint\"): t.AnyTypeAnnotation;\n};\n\n/**\n * Create a type annotation based on typeof expression.\n */\nfunction createTypeAnnotationBasedOnTypeof(type: string): t.FlowType {\n switch (type) {\n case \"string\":\n return stringTypeAnnotation();\n case \"number\":\n return numberTypeAnnotation();\n case \"undefined\":\n return voidTypeAnnotation();\n case \"boolean\":\n return booleanTypeAnnotation();\n case \"function\":\n return genericTypeAnnotation(identifier(\"Function\"));\n case \"object\":\n return genericTypeAnnotation(identifier(\"Object\"));\n case \"symbol\":\n return genericTypeAnnotation(identifier(\"Symbol\"));\n case \"bigint\":\n // todo: use BigInt annotation when Flow supports BigInt\n // https://github.com/facebook/flow/issues/6639\n return anyTypeAnnotation();\n }\n throw new Error(\"Invalid typeof value: \" + type);\n}\n"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAQ+B,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GAGhBC,iCAAiC;AAchD,SAASA,iCAAiCA,CAACC,IAAY,EAAc;EACnE,QAAQA,IAAI;IACV,KAAK,QAAQ;MACX,OAAO,IAAAC,2BAAoB,EAAC,CAAC;IAC/B,KAAK,QAAQ;MACX,OAAO,IAAAC,2BAAoB,EAAC,CAAC;IAC/B,KAAK,WAAW;MACd,OAAO,IAAAC,yBAAkB,EAAC,CAAC;IAC7B,KAAK,SAAS;MACZ,OAAO,IAAAC,4BAAqB,EAAC,CAAC;IAChC,KAAK,UAAU;MACb,OAAO,IAAAC,4BAAqB,EAAC,IAAAC,iBAAU,EAAC,UAAU,CAAC,CAAC;IACtD,KAAK,QAAQ;MACX,OAAO,IAAAD,4BAAqB,EAAC,IAAAC,iBAAU,EAAC,QAAQ,CAAC,CAAC;IACpD,KAAK,QAAQ;MACX,OAAO,IAAAD,4BAAqB,EAAC,IAAAC,iBAAU,EAAC,QAAQ,CAAC,CAAC;IACpD,KAAK,QAAQ;MAGX,OAAO,IAAAC,wBAAiB,EAAC,CAAC;EAC9B;EACA,MAAM,IAAIC,KAAK,CAAC,wBAAwB,GAAGR,IAAI,CAAC;AAClD","ignoreList":[]}
|
||||
29
pmoapp/webapp/node_modules/@babel/types/lib/builders/generated/index.js
generated
vendored
Normal file
29
pmoapp/webapp/node_modules/@babel/types/lib/builders/generated/index.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
var _lowercase = require("./lowercase.js");
|
||||
Object.keys(_lowercase).forEach(function (key) {
|
||||
if (key === "default" || key === "__esModule") return;
|
||||
if (key in exports && exports[key] === _lowercase[key]) return;
|
||||
Object.defineProperty(exports, key, {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _lowercase[key];
|
||||
}
|
||||
});
|
||||
});
|
||||
var _uppercase = require("./uppercase.js");
|
||||
Object.keys(_uppercase).forEach(function (key) {
|
||||
if (key === "default" || key === "__esModule") return;
|
||||
if (key in exports && exports[key] === _uppercase[key]) return;
|
||||
Object.defineProperty(exports, key, {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _uppercase[key];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/generated/index.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/generated/index.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2896
pmoapp/webapp/node_modules/@babel/types/lib/builders/generated/lowercase.js
generated
vendored
Normal file
2896
pmoapp/webapp/node_modules/@babel/types/lib/builders/generated/lowercase.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/generated/lowercase.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/generated/lowercase.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
274
pmoapp/webapp/node_modules/@babel/types/lib/builders/generated/uppercase.js
generated
vendored
Normal file
274
pmoapp/webapp/node_modules/@babel/types/lib/builders/generated/uppercase.js
generated
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.JSXIdentifier = exports.JSXFragment = exports.JSXExpressionContainer = exports.JSXEmptyExpression = exports.JSXElement = exports.JSXClosingFragment = exports.JSXClosingElement = exports.JSXAttribute = exports.IntersectionTypeAnnotation = exports.InterpreterDirective = exports.InterfaceTypeAnnotation = exports.InterfaceExtends = exports.InterfaceDeclaration = exports.InferredPredicate = exports.IndexedAccessType = exports.ImportSpecifier = exports.ImportNamespaceSpecifier = exports.ImportExpression = exports.ImportDefaultSpecifier = exports.ImportDeclaration = exports.ImportAttribute = exports.Import = exports.IfStatement = exports.Identifier = exports.GenericTypeAnnotation = exports.FunctionTypeParam = exports.FunctionTypeAnnotation = exports.FunctionExpression = exports.FunctionDeclaration = exports.ForStatement = exports.ForOfStatement = exports.ForInStatement = exports.File = exports.ExpressionStatement = exports.ExportSpecifier = exports.ExportNamespaceSpecifier = exports.ExportNamedDeclaration = exports.ExportDefaultSpecifier = exports.ExportDefaultDeclaration = exports.ExportAllDeclaration = exports.ExistsTypeAnnotation = exports.EnumSymbolBody = exports.EnumStringMember = exports.EnumStringBody = exports.EnumNumberMember = exports.EnumNumberBody = exports.EnumDefaultedMember = exports.EnumDeclaration = exports.EnumBooleanMember = exports.EnumBooleanBody = exports.EmptyTypeAnnotation = exports.EmptyStatement = exports.DoWhileStatement = exports.DoExpression = exports.DirectiveLiteral = exports.Directive = exports.Decorator = exports.DeclaredPredicate = exports.DeclareVariable = exports.DeclareTypeAlias = exports.DeclareOpaqueType = exports.DeclareModuleExports = exports.DeclareModule = exports.DeclareInterface = exports.DeclareFunction = exports.DeclareExportDeclaration = exports.DeclareExportAllDeclaration = exports.DeclareClass = exports.DecimalLiteral = exports.DebuggerStatement = exports.ContinueStatement = exports.ConditionalExpression = exports.ClassProperty = exports.ClassPrivateProperty = exports.ClassPrivateMethod = exports.ClassMethod = exports.ClassImplements = exports.ClassExpression = exports.ClassDeclaration = exports.ClassBody = exports.ClassAccessorProperty = exports.CatchClause = exports.CallExpression = exports.BreakStatement = exports.BooleanTypeAnnotation = exports.BooleanLiteralTypeAnnotation = exports.BooleanLiteral = exports.BlockStatement = exports.BindExpression = exports.BinaryExpression = exports.BigIntLiteral = exports.AwaitExpression = exports.AssignmentPattern = exports.AssignmentExpression = exports.ArrowFunctionExpression = exports.ArrayTypeAnnotation = exports.ArrayPattern = exports.ArrayExpression = exports.ArgumentPlaceholder = exports.AnyTypeAnnotation = void 0;
|
||||
exports.TSNumberKeyword = exports.TSNullKeyword = exports.TSNonNullExpression = exports.TSNeverKeyword = exports.TSNamespaceExportDeclaration = exports.TSNamedTupleMember = exports.TSModuleDeclaration = exports.TSModuleBlock = exports.TSMethodSignature = exports.TSMappedType = exports.TSLiteralType = exports.TSIntrinsicKeyword = exports.TSIntersectionType = exports.TSInterfaceDeclaration = exports.TSInterfaceBody = exports.TSInstantiationExpression = exports.TSInferType = exports.TSIndexedAccessType = exports.TSIndexSignature = exports.TSImportType = exports.TSImportEqualsDeclaration = exports.TSFunctionType = exports.TSExternalModuleReference = exports.TSExpressionWithTypeArguments = exports.TSExportAssignment = exports.TSEnumMember = exports.TSEnumDeclaration = exports.TSEnumBody = exports.TSDeclareMethod = exports.TSDeclareFunction = exports.TSConstructorType = exports.TSConstructSignatureDeclaration = exports.TSConditionalType = exports.TSCallSignatureDeclaration = exports.TSBooleanKeyword = exports.TSBigIntKeyword = exports.TSAsExpression = exports.TSArrayType = exports.TSAnyKeyword = exports.SymbolTypeAnnotation = exports.SwitchStatement = exports.SwitchCase = exports.Super = exports.StringTypeAnnotation = exports.StringLiteralTypeAnnotation = exports.StringLiteral = exports.StaticBlock = exports.SpreadProperty = exports.SpreadElement = exports.SequenceExpression = exports.ReturnStatement = exports.RestProperty = exports.RestElement = exports.RegexLiteral = exports.RegExpLiteral = exports.RecordExpression = exports.QualifiedTypeIdentifier = exports.Program = exports.PrivateName = exports.Placeholder = exports.PipelineTopicExpression = exports.PipelinePrimaryTopicReference = exports.PipelineBareFunction = exports.ParenthesizedExpression = exports.OptionalMemberExpression = exports.OptionalIndexedAccessType = exports.OptionalCallExpression = exports.OpaqueType = exports.ObjectTypeSpreadProperty = exports.ObjectTypeProperty = exports.ObjectTypeInternalSlot = exports.ObjectTypeIndexer = exports.ObjectTypeCallProperty = exports.ObjectTypeAnnotation = exports.ObjectProperty = exports.ObjectPattern = exports.ObjectMethod = exports.ObjectExpression = exports.NumericLiteral = exports.NumberTypeAnnotation = exports.NumberLiteralTypeAnnotation = exports.NumberLiteral = exports.NullableTypeAnnotation = exports.NullLiteralTypeAnnotation = exports.NullLiteral = exports.Noop = exports.NewExpression = exports.ModuleExpression = exports.MixedTypeAnnotation = exports.MetaProperty = exports.MemberExpression = exports.LogicalExpression = exports.LabeledStatement = exports.JSXText = exports.JSXSpreadChild = exports.JSXSpreadAttribute = exports.JSXOpeningFragment = exports.JSXOpeningElement = exports.JSXNamespacedName = exports.JSXMemberExpression = void 0;
|
||||
exports.YieldExpression = exports.WithStatement = exports.WhileStatement = exports.VoidTypeAnnotation = exports.VoidPattern = exports.Variance = exports.VariableDeclarator = exports.VariableDeclaration = exports.V8IntrinsicIdentifier = exports.UpdateExpression = exports.UnionTypeAnnotation = exports.UnaryExpression = exports.TypeofTypeAnnotation = exports.TypeParameterInstantiation = exports.TypeParameterDeclaration = exports.TypeParameter = exports.TypeCastExpression = exports.TypeAnnotation = exports.TypeAlias = exports.TupleTypeAnnotation = exports.TupleExpression = exports.TryStatement = exports.TopicReference = exports.ThrowStatement = exports.ThisTypeAnnotation = exports.ThisExpression = exports.TemplateLiteral = exports.TemplateElement = exports.TaggedTemplateExpression = exports.TSVoidKeyword = exports.TSUnknownKeyword = exports.TSUnionType = exports.TSUndefinedKeyword = exports.TSTypeReference = exports.TSTypeQuery = exports.TSTypePredicate = exports.TSTypeParameterInstantiation = exports.TSTypeParameterDeclaration = exports.TSTypeParameter = exports.TSTypeOperator = exports.TSTypeLiteral = exports.TSTypeAssertion = exports.TSTypeAnnotation = exports.TSTypeAliasDeclaration = exports.TSTupleType = exports.TSThisType = exports.TSTemplateLiteralType = exports.TSSymbolKeyword = exports.TSStringKeyword = exports.TSSatisfiesExpression = exports.TSRestType = exports.TSQualifiedName = exports.TSPropertySignature = exports.TSParenthesizedType = exports.TSParameterProperty = exports.TSOptionalType = exports.TSObjectKeyword = void 0;
|
||||
var b = require("./lowercase.js");
|
||||
var _deprecationWarning = require("../../utils/deprecationWarning.js");
|
||||
function alias(lowercase) {
|
||||
{
|
||||
return b[lowercase];
|
||||
}
|
||||
}
|
||||
const ArrayExpression = exports.ArrayExpression = alias("arrayExpression"),
|
||||
AssignmentExpression = exports.AssignmentExpression = alias("assignmentExpression"),
|
||||
BinaryExpression = exports.BinaryExpression = alias("binaryExpression"),
|
||||
InterpreterDirective = exports.InterpreterDirective = alias("interpreterDirective"),
|
||||
Directive = exports.Directive = alias("directive"),
|
||||
DirectiveLiteral = exports.DirectiveLiteral = alias("directiveLiteral"),
|
||||
BlockStatement = exports.BlockStatement = alias("blockStatement"),
|
||||
BreakStatement = exports.BreakStatement = alias("breakStatement"),
|
||||
CallExpression = exports.CallExpression = alias("callExpression"),
|
||||
CatchClause = exports.CatchClause = alias("catchClause"),
|
||||
ConditionalExpression = exports.ConditionalExpression = alias("conditionalExpression"),
|
||||
ContinueStatement = exports.ContinueStatement = alias("continueStatement"),
|
||||
DebuggerStatement = exports.DebuggerStatement = alias("debuggerStatement"),
|
||||
DoWhileStatement = exports.DoWhileStatement = alias("doWhileStatement"),
|
||||
EmptyStatement = exports.EmptyStatement = alias("emptyStatement"),
|
||||
ExpressionStatement = exports.ExpressionStatement = alias("expressionStatement"),
|
||||
File = exports.File = alias("file"),
|
||||
ForInStatement = exports.ForInStatement = alias("forInStatement"),
|
||||
ForStatement = exports.ForStatement = alias("forStatement"),
|
||||
FunctionDeclaration = exports.FunctionDeclaration = alias("functionDeclaration"),
|
||||
FunctionExpression = exports.FunctionExpression = alias("functionExpression"),
|
||||
Identifier = exports.Identifier = alias("identifier"),
|
||||
IfStatement = exports.IfStatement = alias("ifStatement"),
|
||||
LabeledStatement = exports.LabeledStatement = alias("labeledStatement"),
|
||||
StringLiteral = exports.StringLiteral = alias("stringLiteral"),
|
||||
NumericLiteral = exports.NumericLiteral = alias("numericLiteral"),
|
||||
NullLiteral = exports.NullLiteral = alias("nullLiteral"),
|
||||
BooleanLiteral = exports.BooleanLiteral = alias("booleanLiteral"),
|
||||
RegExpLiteral = exports.RegExpLiteral = alias("regExpLiteral"),
|
||||
LogicalExpression = exports.LogicalExpression = alias("logicalExpression"),
|
||||
MemberExpression = exports.MemberExpression = alias("memberExpression"),
|
||||
NewExpression = exports.NewExpression = alias("newExpression"),
|
||||
Program = exports.Program = alias("program"),
|
||||
ObjectExpression = exports.ObjectExpression = alias("objectExpression"),
|
||||
ObjectMethod = exports.ObjectMethod = alias("objectMethod"),
|
||||
ObjectProperty = exports.ObjectProperty = alias("objectProperty"),
|
||||
RestElement = exports.RestElement = alias("restElement"),
|
||||
ReturnStatement = exports.ReturnStatement = alias("returnStatement"),
|
||||
SequenceExpression = exports.SequenceExpression = alias("sequenceExpression"),
|
||||
ParenthesizedExpression = exports.ParenthesizedExpression = alias("parenthesizedExpression"),
|
||||
SwitchCase = exports.SwitchCase = alias("switchCase"),
|
||||
SwitchStatement = exports.SwitchStatement = alias("switchStatement"),
|
||||
ThisExpression = exports.ThisExpression = alias("thisExpression"),
|
||||
ThrowStatement = exports.ThrowStatement = alias("throwStatement"),
|
||||
TryStatement = exports.TryStatement = alias("tryStatement"),
|
||||
UnaryExpression = exports.UnaryExpression = alias("unaryExpression"),
|
||||
UpdateExpression = exports.UpdateExpression = alias("updateExpression"),
|
||||
VariableDeclaration = exports.VariableDeclaration = alias("variableDeclaration"),
|
||||
VariableDeclarator = exports.VariableDeclarator = alias("variableDeclarator"),
|
||||
WhileStatement = exports.WhileStatement = alias("whileStatement"),
|
||||
WithStatement = exports.WithStatement = alias("withStatement"),
|
||||
AssignmentPattern = exports.AssignmentPattern = alias("assignmentPattern"),
|
||||
ArrayPattern = exports.ArrayPattern = alias("arrayPattern"),
|
||||
ArrowFunctionExpression = exports.ArrowFunctionExpression = alias("arrowFunctionExpression"),
|
||||
ClassBody = exports.ClassBody = alias("classBody"),
|
||||
ClassExpression = exports.ClassExpression = alias("classExpression"),
|
||||
ClassDeclaration = exports.ClassDeclaration = alias("classDeclaration"),
|
||||
ExportAllDeclaration = exports.ExportAllDeclaration = alias("exportAllDeclaration"),
|
||||
ExportDefaultDeclaration = exports.ExportDefaultDeclaration = alias("exportDefaultDeclaration"),
|
||||
ExportNamedDeclaration = exports.ExportNamedDeclaration = alias("exportNamedDeclaration"),
|
||||
ExportSpecifier = exports.ExportSpecifier = alias("exportSpecifier"),
|
||||
ForOfStatement = exports.ForOfStatement = alias("forOfStatement"),
|
||||
ImportDeclaration = exports.ImportDeclaration = alias("importDeclaration"),
|
||||
ImportDefaultSpecifier = exports.ImportDefaultSpecifier = alias("importDefaultSpecifier"),
|
||||
ImportNamespaceSpecifier = exports.ImportNamespaceSpecifier = alias("importNamespaceSpecifier"),
|
||||
ImportSpecifier = exports.ImportSpecifier = alias("importSpecifier"),
|
||||
ImportExpression = exports.ImportExpression = alias("importExpression"),
|
||||
MetaProperty = exports.MetaProperty = alias("metaProperty"),
|
||||
ClassMethod = exports.ClassMethod = alias("classMethod"),
|
||||
ObjectPattern = exports.ObjectPattern = alias("objectPattern"),
|
||||
SpreadElement = exports.SpreadElement = alias("spreadElement"),
|
||||
Super = exports.Super = alias("super"),
|
||||
TaggedTemplateExpression = exports.TaggedTemplateExpression = alias("taggedTemplateExpression"),
|
||||
TemplateElement = exports.TemplateElement = alias("templateElement"),
|
||||
TemplateLiteral = exports.TemplateLiteral = alias("templateLiteral"),
|
||||
YieldExpression = exports.YieldExpression = alias("yieldExpression"),
|
||||
AwaitExpression = exports.AwaitExpression = alias("awaitExpression"),
|
||||
Import = exports.Import = alias("import"),
|
||||
BigIntLiteral = exports.BigIntLiteral = alias("bigIntLiteral"),
|
||||
ExportNamespaceSpecifier = exports.ExportNamespaceSpecifier = alias("exportNamespaceSpecifier"),
|
||||
OptionalMemberExpression = exports.OptionalMemberExpression = alias("optionalMemberExpression"),
|
||||
OptionalCallExpression = exports.OptionalCallExpression = alias("optionalCallExpression"),
|
||||
ClassProperty = exports.ClassProperty = alias("classProperty"),
|
||||
ClassAccessorProperty = exports.ClassAccessorProperty = alias("classAccessorProperty"),
|
||||
ClassPrivateProperty = exports.ClassPrivateProperty = alias("classPrivateProperty"),
|
||||
ClassPrivateMethod = exports.ClassPrivateMethod = alias("classPrivateMethod"),
|
||||
PrivateName = exports.PrivateName = alias("privateName"),
|
||||
StaticBlock = exports.StaticBlock = alias("staticBlock"),
|
||||
ImportAttribute = exports.ImportAttribute = alias("importAttribute"),
|
||||
AnyTypeAnnotation = exports.AnyTypeAnnotation = alias("anyTypeAnnotation"),
|
||||
ArrayTypeAnnotation = exports.ArrayTypeAnnotation = alias("arrayTypeAnnotation"),
|
||||
BooleanTypeAnnotation = exports.BooleanTypeAnnotation = alias("booleanTypeAnnotation"),
|
||||
BooleanLiteralTypeAnnotation = exports.BooleanLiteralTypeAnnotation = alias("booleanLiteralTypeAnnotation"),
|
||||
NullLiteralTypeAnnotation = exports.NullLiteralTypeAnnotation = alias("nullLiteralTypeAnnotation"),
|
||||
ClassImplements = exports.ClassImplements = alias("classImplements"),
|
||||
DeclareClass = exports.DeclareClass = alias("declareClass"),
|
||||
DeclareFunction = exports.DeclareFunction = alias("declareFunction"),
|
||||
DeclareInterface = exports.DeclareInterface = alias("declareInterface"),
|
||||
DeclareModule = exports.DeclareModule = alias("declareModule"),
|
||||
DeclareModuleExports = exports.DeclareModuleExports = alias("declareModuleExports"),
|
||||
DeclareTypeAlias = exports.DeclareTypeAlias = alias("declareTypeAlias"),
|
||||
DeclareOpaqueType = exports.DeclareOpaqueType = alias("declareOpaqueType"),
|
||||
DeclareVariable = exports.DeclareVariable = alias("declareVariable"),
|
||||
DeclareExportDeclaration = exports.DeclareExportDeclaration = alias("declareExportDeclaration"),
|
||||
DeclareExportAllDeclaration = exports.DeclareExportAllDeclaration = alias("declareExportAllDeclaration"),
|
||||
DeclaredPredicate = exports.DeclaredPredicate = alias("declaredPredicate"),
|
||||
ExistsTypeAnnotation = exports.ExistsTypeAnnotation = alias("existsTypeAnnotation"),
|
||||
FunctionTypeAnnotation = exports.FunctionTypeAnnotation = alias("functionTypeAnnotation"),
|
||||
FunctionTypeParam = exports.FunctionTypeParam = alias("functionTypeParam"),
|
||||
GenericTypeAnnotation = exports.GenericTypeAnnotation = alias("genericTypeAnnotation"),
|
||||
InferredPredicate = exports.InferredPredicate = alias("inferredPredicate"),
|
||||
InterfaceExtends = exports.InterfaceExtends = alias("interfaceExtends"),
|
||||
InterfaceDeclaration = exports.InterfaceDeclaration = alias("interfaceDeclaration"),
|
||||
InterfaceTypeAnnotation = exports.InterfaceTypeAnnotation = alias("interfaceTypeAnnotation"),
|
||||
IntersectionTypeAnnotation = exports.IntersectionTypeAnnotation = alias("intersectionTypeAnnotation"),
|
||||
MixedTypeAnnotation = exports.MixedTypeAnnotation = alias("mixedTypeAnnotation"),
|
||||
EmptyTypeAnnotation = exports.EmptyTypeAnnotation = alias("emptyTypeAnnotation"),
|
||||
NullableTypeAnnotation = exports.NullableTypeAnnotation = alias("nullableTypeAnnotation"),
|
||||
NumberLiteralTypeAnnotation = exports.NumberLiteralTypeAnnotation = alias("numberLiteralTypeAnnotation"),
|
||||
NumberTypeAnnotation = exports.NumberTypeAnnotation = alias("numberTypeAnnotation"),
|
||||
ObjectTypeAnnotation = exports.ObjectTypeAnnotation = alias("objectTypeAnnotation"),
|
||||
ObjectTypeInternalSlot = exports.ObjectTypeInternalSlot = alias("objectTypeInternalSlot"),
|
||||
ObjectTypeCallProperty = exports.ObjectTypeCallProperty = alias("objectTypeCallProperty"),
|
||||
ObjectTypeIndexer = exports.ObjectTypeIndexer = alias("objectTypeIndexer"),
|
||||
ObjectTypeProperty = exports.ObjectTypeProperty = alias("objectTypeProperty"),
|
||||
ObjectTypeSpreadProperty = exports.ObjectTypeSpreadProperty = alias("objectTypeSpreadProperty"),
|
||||
OpaqueType = exports.OpaqueType = alias("opaqueType"),
|
||||
QualifiedTypeIdentifier = exports.QualifiedTypeIdentifier = alias("qualifiedTypeIdentifier"),
|
||||
StringLiteralTypeAnnotation = exports.StringLiteralTypeAnnotation = alias("stringLiteralTypeAnnotation"),
|
||||
StringTypeAnnotation = exports.StringTypeAnnotation = alias("stringTypeAnnotation"),
|
||||
SymbolTypeAnnotation = exports.SymbolTypeAnnotation = alias("symbolTypeAnnotation"),
|
||||
ThisTypeAnnotation = exports.ThisTypeAnnotation = alias("thisTypeAnnotation"),
|
||||
TupleTypeAnnotation = exports.TupleTypeAnnotation = alias("tupleTypeAnnotation"),
|
||||
TypeofTypeAnnotation = exports.TypeofTypeAnnotation = alias("typeofTypeAnnotation"),
|
||||
TypeAlias = exports.TypeAlias = alias("typeAlias"),
|
||||
TypeAnnotation = exports.TypeAnnotation = alias("typeAnnotation"),
|
||||
TypeCastExpression = exports.TypeCastExpression = alias("typeCastExpression"),
|
||||
TypeParameter = exports.TypeParameter = alias("typeParameter"),
|
||||
TypeParameterDeclaration = exports.TypeParameterDeclaration = alias("typeParameterDeclaration"),
|
||||
TypeParameterInstantiation = exports.TypeParameterInstantiation = alias("typeParameterInstantiation"),
|
||||
UnionTypeAnnotation = exports.UnionTypeAnnotation = alias("unionTypeAnnotation"),
|
||||
Variance = exports.Variance = alias("variance"),
|
||||
VoidTypeAnnotation = exports.VoidTypeAnnotation = alias("voidTypeAnnotation"),
|
||||
EnumDeclaration = exports.EnumDeclaration = alias("enumDeclaration"),
|
||||
EnumBooleanBody = exports.EnumBooleanBody = alias("enumBooleanBody"),
|
||||
EnumNumberBody = exports.EnumNumberBody = alias("enumNumberBody"),
|
||||
EnumStringBody = exports.EnumStringBody = alias("enumStringBody"),
|
||||
EnumSymbolBody = exports.EnumSymbolBody = alias("enumSymbolBody"),
|
||||
EnumBooleanMember = exports.EnumBooleanMember = alias("enumBooleanMember"),
|
||||
EnumNumberMember = exports.EnumNumberMember = alias("enumNumberMember"),
|
||||
EnumStringMember = exports.EnumStringMember = alias("enumStringMember"),
|
||||
EnumDefaultedMember = exports.EnumDefaultedMember = alias("enumDefaultedMember"),
|
||||
IndexedAccessType = exports.IndexedAccessType = alias("indexedAccessType"),
|
||||
OptionalIndexedAccessType = exports.OptionalIndexedAccessType = alias("optionalIndexedAccessType"),
|
||||
JSXAttribute = exports.JSXAttribute = alias("jsxAttribute"),
|
||||
JSXClosingElement = exports.JSXClosingElement = alias("jsxClosingElement"),
|
||||
JSXElement = exports.JSXElement = alias("jsxElement"),
|
||||
JSXEmptyExpression = exports.JSXEmptyExpression = alias("jsxEmptyExpression"),
|
||||
JSXExpressionContainer = exports.JSXExpressionContainer = alias("jsxExpressionContainer"),
|
||||
JSXSpreadChild = exports.JSXSpreadChild = alias("jsxSpreadChild"),
|
||||
JSXIdentifier = exports.JSXIdentifier = alias("jsxIdentifier"),
|
||||
JSXMemberExpression = exports.JSXMemberExpression = alias("jsxMemberExpression"),
|
||||
JSXNamespacedName = exports.JSXNamespacedName = alias("jsxNamespacedName"),
|
||||
JSXOpeningElement = exports.JSXOpeningElement = alias("jsxOpeningElement"),
|
||||
JSXSpreadAttribute = exports.JSXSpreadAttribute = alias("jsxSpreadAttribute"),
|
||||
JSXText = exports.JSXText = alias("jsxText"),
|
||||
JSXFragment = exports.JSXFragment = alias("jsxFragment"),
|
||||
JSXOpeningFragment = exports.JSXOpeningFragment = alias("jsxOpeningFragment"),
|
||||
JSXClosingFragment = exports.JSXClosingFragment = alias("jsxClosingFragment"),
|
||||
Noop = exports.Noop = alias("noop"),
|
||||
Placeholder = exports.Placeholder = alias("placeholder"),
|
||||
V8IntrinsicIdentifier = exports.V8IntrinsicIdentifier = alias("v8IntrinsicIdentifier"),
|
||||
ArgumentPlaceholder = exports.ArgumentPlaceholder = alias("argumentPlaceholder"),
|
||||
BindExpression = exports.BindExpression = alias("bindExpression"),
|
||||
Decorator = exports.Decorator = alias("decorator"),
|
||||
DoExpression = exports.DoExpression = alias("doExpression"),
|
||||
ExportDefaultSpecifier = exports.ExportDefaultSpecifier = alias("exportDefaultSpecifier"),
|
||||
RecordExpression = exports.RecordExpression = alias("recordExpression"),
|
||||
TupleExpression = exports.TupleExpression = alias("tupleExpression"),
|
||||
DecimalLiteral = exports.DecimalLiteral = alias("decimalLiteral"),
|
||||
ModuleExpression = exports.ModuleExpression = alias("moduleExpression"),
|
||||
TopicReference = exports.TopicReference = alias("topicReference"),
|
||||
PipelineTopicExpression = exports.PipelineTopicExpression = alias("pipelineTopicExpression"),
|
||||
PipelineBareFunction = exports.PipelineBareFunction = alias("pipelineBareFunction"),
|
||||
PipelinePrimaryTopicReference = exports.PipelinePrimaryTopicReference = alias("pipelinePrimaryTopicReference"),
|
||||
VoidPattern = exports.VoidPattern = alias("voidPattern"),
|
||||
TSParameterProperty = exports.TSParameterProperty = alias("tsParameterProperty"),
|
||||
TSDeclareFunction = exports.TSDeclareFunction = alias("tsDeclareFunction"),
|
||||
TSDeclareMethod = exports.TSDeclareMethod = alias("tsDeclareMethod"),
|
||||
TSQualifiedName = exports.TSQualifiedName = alias("tsQualifiedName"),
|
||||
TSCallSignatureDeclaration = exports.TSCallSignatureDeclaration = alias("tsCallSignatureDeclaration"),
|
||||
TSConstructSignatureDeclaration = exports.TSConstructSignatureDeclaration = alias("tsConstructSignatureDeclaration"),
|
||||
TSPropertySignature = exports.TSPropertySignature = alias("tsPropertySignature"),
|
||||
TSMethodSignature = exports.TSMethodSignature = alias("tsMethodSignature"),
|
||||
TSIndexSignature = exports.TSIndexSignature = alias("tsIndexSignature"),
|
||||
TSAnyKeyword = exports.TSAnyKeyword = alias("tsAnyKeyword"),
|
||||
TSBooleanKeyword = exports.TSBooleanKeyword = alias("tsBooleanKeyword"),
|
||||
TSBigIntKeyword = exports.TSBigIntKeyword = alias("tsBigIntKeyword"),
|
||||
TSIntrinsicKeyword = exports.TSIntrinsicKeyword = alias("tsIntrinsicKeyword"),
|
||||
TSNeverKeyword = exports.TSNeverKeyword = alias("tsNeverKeyword"),
|
||||
TSNullKeyword = exports.TSNullKeyword = alias("tsNullKeyword"),
|
||||
TSNumberKeyword = exports.TSNumberKeyword = alias("tsNumberKeyword"),
|
||||
TSObjectKeyword = exports.TSObjectKeyword = alias("tsObjectKeyword"),
|
||||
TSStringKeyword = exports.TSStringKeyword = alias("tsStringKeyword"),
|
||||
TSSymbolKeyword = exports.TSSymbolKeyword = alias("tsSymbolKeyword"),
|
||||
TSUndefinedKeyword = exports.TSUndefinedKeyword = alias("tsUndefinedKeyword"),
|
||||
TSUnknownKeyword = exports.TSUnknownKeyword = alias("tsUnknownKeyword"),
|
||||
TSVoidKeyword = exports.TSVoidKeyword = alias("tsVoidKeyword"),
|
||||
TSThisType = exports.TSThisType = alias("tsThisType"),
|
||||
TSFunctionType = exports.TSFunctionType = alias("tsFunctionType"),
|
||||
TSConstructorType = exports.TSConstructorType = alias("tsConstructorType"),
|
||||
TSTypeReference = exports.TSTypeReference = alias("tsTypeReference"),
|
||||
TSTypePredicate = exports.TSTypePredicate = alias("tsTypePredicate"),
|
||||
TSTypeQuery = exports.TSTypeQuery = alias("tsTypeQuery"),
|
||||
TSTypeLiteral = exports.TSTypeLiteral = alias("tsTypeLiteral"),
|
||||
TSArrayType = exports.TSArrayType = alias("tsArrayType"),
|
||||
TSTupleType = exports.TSTupleType = alias("tsTupleType"),
|
||||
TSOptionalType = exports.TSOptionalType = alias("tsOptionalType"),
|
||||
TSRestType = exports.TSRestType = alias("tsRestType"),
|
||||
TSNamedTupleMember = exports.TSNamedTupleMember = alias("tsNamedTupleMember"),
|
||||
TSUnionType = exports.TSUnionType = alias("tsUnionType"),
|
||||
TSIntersectionType = exports.TSIntersectionType = alias("tsIntersectionType"),
|
||||
TSConditionalType = exports.TSConditionalType = alias("tsConditionalType"),
|
||||
TSInferType = exports.TSInferType = alias("tsInferType"),
|
||||
TSParenthesizedType = exports.TSParenthesizedType = alias("tsParenthesizedType"),
|
||||
TSTypeOperator = exports.TSTypeOperator = alias("tsTypeOperator"),
|
||||
TSIndexedAccessType = exports.TSIndexedAccessType = alias("tsIndexedAccessType"),
|
||||
TSMappedType = exports.TSMappedType = alias("tsMappedType"),
|
||||
TSTemplateLiteralType = exports.TSTemplateLiteralType = alias("tsTemplateLiteralType"),
|
||||
TSLiteralType = exports.TSLiteralType = alias("tsLiteralType"),
|
||||
TSExpressionWithTypeArguments = exports.TSExpressionWithTypeArguments = alias("tsExpressionWithTypeArguments"),
|
||||
TSInterfaceDeclaration = exports.TSInterfaceDeclaration = alias("tsInterfaceDeclaration"),
|
||||
TSInterfaceBody = exports.TSInterfaceBody = alias("tsInterfaceBody"),
|
||||
TSTypeAliasDeclaration = exports.TSTypeAliasDeclaration = alias("tsTypeAliasDeclaration"),
|
||||
TSInstantiationExpression = exports.TSInstantiationExpression = alias("tsInstantiationExpression"),
|
||||
TSAsExpression = exports.TSAsExpression = alias("tsAsExpression"),
|
||||
TSSatisfiesExpression = exports.TSSatisfiesExpression = alias("tsSatisfiesExpression"),
|
||||
TSTypeAssertion = exports.TSTypeAssertion = alias("tsTypeAssertion"),
|
||||
TSEnumBody = exports.TSEnumBody = alias("tsEnumBody"),
|
||||
TSEnumDeclaration = exports.TSEnumDeclaration = alias("tsEnumDeclaration"),
|
||||
TSEnumMember = exports.TSEnumMember = alias("tsEnumMember"),
|
||||
TSModuleDeclaration = exports.TSModuleDeclaration = alias("tsModuleDeclaration"),
|
||||
TSModuleBlock = exports.TSModuleBlock = alias("tsModuleBlock"),
|
||||
TSImportType = exports.TSImportType = alias("tsImportType"),
|
||||
TSImportEqualsDeclaration = exports.TSImportEqualsDeclaration = alias("tsImportEqualsDeclaration"),
|
||||
TSExternalModuleReference = exports.TSExternalModuleReference = alias("tsExternalModuleReference"),
|
||||
TSNonNullExpression = exports.TSNonNullExpression = alias("tsNonNullExpression"),
|
||||
TSExportAssignment = exports.TSExportAssignment = alias("tsExportAssignment"),
|
||||
TSNamespaceExportDeclaration = exports.TSNamespaceExportDeclaration = alias("tsNamespaceExportDeclaration"),
|
||||
TSTypeAnnotation = exports.TSTypeAnnotation = alias("tsTypeAnnotation"),
|
||||
TSTypeParameterInstantiation = exports.TSTypeParameterInstantiation = alias("tsTypeParameterInstantiation"),
|
||||
TSTypeParameterDeclaration = exports.TSTypeParameterDeclaration = alias("tsTypeParameterDeclaration"),
|
||||
TSTypeParameter = exports.TSTypeParameter = alias("tsTypeParameter");
|
||||
const NumberLiteral = exports.NumberLiteral = b.numberLiteral,
|
||||
RegexLiteral = exports.RegexLiteral = b.regexLiteral,
|
||||
RestProperty = exports.RestProperty = b.restProperty,
|
||||
SpreadProperty = exports.SpreadProperty = b.spreadProperty;
|
||||
|
||||
//# sourceMappingURL=uppercase.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/generated/uppercase.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/generated/uppercase.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
12
pmoapp/webapp/node_modules/@babel/types/lib/builders/productions.js
generated
vendored
Normal file
12
pmoapp/webapp/node_modules/@babel/types/lib/builders/productions.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.buildUndefinedNode = buildUndefinedNode;
|
||||
var _index = require("./generated/index.js");
|
||||
function buildUndefinedNode() {
|
||||
return (0, _index.unaryExpression)("void", (0, _index.numericLiteral)(0), true);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=productions.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/productions.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/productions.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"names":["_index","require","buildUndefinedNode","unaryExpression","numericLiteral"],"sources":["../../src/builders/productions.ts"],"sourcesContent":["import { numericLiteral, unaryExpression } from \"./generated/index.ts\";\n\nexport function buildUndefinedNode() {\n return unaryExpression(\"void\", numericLiteral(0), true);\n}\n"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAEO,SAASC,kBAAkBA,CAAA,EAAG;EACnC,OAAO,IAAAC,sBAAe,EAAC,MAAM,EAAE,IAAAC,qBAAc,EAAC,CAAC,CAAC,EAAE,IAAI,CAAC;AACzD","ignoreList":[]}
|
||||
24
pmoapp/webapp/node_modules/@babel/types/lib/builders/react/buildChildren.js
generated
vendored
Normal file
24
pmoapp/webapp/node_modules/@babel/types/lib/builders/react/buildChildren.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = buildChildren;
|
||||
var _index = require("../../validators/generated/index.js");
|
||||
var _cleanJSXElementLiteralChild = require("../../utils/react/cleanJSXElementLiteralChild.js");
|
||||
function buildChildren(node) {
|
||||
const elements = [];
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
let child = node.children[i];
|
||||
if ((0, _index.isJSXText)(child)) {
|
||||
(0, _cleanJSXElementLiteralChild.default)(child, elements);
|
||||
continue;
|
||||
}
|
||||
if ((0, _index.isJSXExpressionContainer)(child)) child = child.expression;
|
||||
if ((0, _index.isJSXEmptyExpression)(child)) continue;
|
||||
elements.push(child);
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=buildChildren.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/react/buildChildren.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/react/buildChildren.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"names":["_index","require","_cleanJSXElementLiteralChild","buildChildren","node","elements","i","children","length","child","isJSXText","cleanJSXElementLiteralChild","isJSXExpressionContainer","expression","isJSXEmptyExpression","push"],"sources":["../../../src/builders/react/buildChildren.ts"],"sourcesContent":["import {\n isJSXText,\n isJSXExpressionContainer,\n isJSXEmptyExpression,\n} from \"../../validators/generated/index.ts\";\nimport cleanJSXElementLiteralChild from \"../../utils/react/cleanJSXElementLiteralChild.ts\";\nimport type * as t from \"../../index.ts\";\n\ntype ReturnedChild =\n | t.JSXSpreadChild\n | t.JSXElement\n | t.JSXFragment\n | t.Expression;\n\nexport default function buildChildren(\n node: t.JSXElement | t.JSXFragment,\n): ReturnedChild[] {\n const elements = [];\n\n for (let i = 0; i < node.children.length; i++) {\n let child: any = node.children[i];\n\n if (isJSXText(child)) {\n cleanJSXElementLiteralChild(child, elements);\n continue;\n }\n\n if (isJSXExpressionContainer(child)) child = child.expression;\n if (isJSXEmptyExpression(child)) continue;\n\n elements.push(child);\n }\n\n return elements;\n}\n"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAKA,IAAAC,4BAAA,GAAAD,OAAA;AASe,SAASE,aAAaA,CACnCC,IAAkC,EACjB;EACjB,MAAMC,QAAQ,GAAG,EAAE;EAEnB,KAAK,IAAIC,CAAC,GAAG,CAAC,EAAEA,CAAC,GAAGF,IAAI,CAACG,QAAQ,CAACC,MAAM,EAAEF,CAAC,EAAE,EAAE;IAC7C,IAAIG,KAAU,GAAGL,IAAI,CAACG,QAAQ,CAACD,CAAC,CAAC;IAEjC,IAAI,IAAAI,gBAAS,EAACD,KAAK,CAAC,EAAE;MACpB,IAAAE,oCAA2B,EAACF,KAAK,EAAEJ,QAAQ,CAAC;MAC5C;IACF;IAEA,IAAI,IAAAO,+BAAwB,EAACH,KAAK,CAAC,EAAEA,KAAK,GAAGA,KAAK,CAACI,UAAU;IAC7D,IAAI,IAAAC,2BAAoB,EAACL,KAAK,CAAC,EAAE;IAEjCJ,QAAQ,CAACU,IAAI,CAACN,KAAK,CAAC;EACtB;EAEA,OAAOJ,QAAQ;AACjB","ignoreList":[]}
|
||||
22
pmoapp/webapp/node_modules/@babel/types/lib/builders/typescript/createTSUnionType.js
generated
vendored
Normal file
22
pmoapp/webapp/node_modules/@babel/types/lib/builders/typescript/createTSUnionType.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = createTSUnionType;
|
||||
var _index = require("../generated/index.js");
|
||||
var _removeTypeDuplicates = require("../../modifications/typescript/removeTypeDuplicates.js");
|
||||
var _index2 = require("../../validators/generated/index.js");
|
||||
function createTSUnionType(typeAnnotations) {
|
||||
const types = typeAnnotations.map(type => {
|
||||
return (0, _index2.isTSTypeAnnotation)(type) ? type.typeAnnotation : type;
|
||||
});
|
||||
const flattened = (0, _removeTypeDuplicates.default)(types);
|
||||
if (flattened.length === 1) {
|
||||
return flattened[0];
|
||||
} else {
|
||||
return (0, _index.tsUnionType)(flattened);
|
||||
}
|
||||
}
|
||||
|
||||
//# sourceMappingURL=createTSUnionType.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/typescript/createTSUnionType.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/typescript/createTSUnionType.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"names":["_index","require","_removeTypeDuplicates","_index2","createTSUnionType","typeAnnotations","types","map","type","isTSTypeAnnotation","typeAnnotation","flattened","removeTypeDuplicates","length","tsUnionType"],"sources":["../../../src/builders/typescript/createTSUnionType.ts"],"sourcesContent":["import { tsUnionType } from \"../generated/index.ts\";\nimport removeTypeDuplicates from \"../../modifications/typescript/removeTypeDuplicates.ts\";\nimport { isTSTypeAnnotation } from \"../../validators/generated/index.ts\";\nimport type * as t from \"../../index.ts\";\n\n/**\n * Takes an array of `types` and flattens them, removing duplicates and\n * returns a `UnionTypeAnnotation` node containing them.\n */\nexport default function createTSUnionType(\n typeAnnotations: Array<t.TSTypeAnnotation | t.TSType>,\n): t.TSType {\n const types = typeAnnotations.map(type => {\n return isTSTypeAnnotation(type) ? type.typeAnnotation : type;\n });\n const flattened = removeTypeDuplicates(types);\n\n if (flattened.length === 1) {\n return flattened[0];\n } else {\n return tsUnionType(flattened);\n }\n}\n"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,qBAAA,GAAAD,OAAA;AACA,IAAAE,OAAA,GAAAF,OAAA;AAOe,SAASG,iBAAiBA,CACvCC,eAAqD,EAC3C;EACV,MAAMC,KAAK,GAAGD,eAAe,CAACE,GAAG,CAACC,IAAI,IAAI;IACxC,OAAO,IAAAC,0BAAkB,EAACD,IAAI,CAAC,GAAGA,IAAI,CAACE,cAAc,GAAGF,IAAI;EAC9D,CAAC,CAAC;EACF,MAAMG,SAAS,GAAG,IAAAC,6BAAoB,EAACN,KAAK,CAAC;EAE7C,IAAIK,SAAS,CAACE,MAAM,KAAK,CAAC,EAAE;IAC1B,OAAOF,SAAS,CAAC,CAAC,CAAC;EACrB,CAAC,MAAM;IACL,OAAO,IAAAG,kBAAW,EAACH,SAAS,CAAC;EAC/B;AACF","ignoreList":[]}
|
||||
21
pmoapp/webapp/node_modules/@babel/types/lib/builders/validateNode.js
generated
vendored
Normal file
21
pmoapp/webapp/node_modules/@babel/types/lib/builders/validateNode.js
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = validateNode;
|
||||
var _validate = require("../validators/validate.js");
|
||||
var _index = require("../index.js");
|
||||
function validateNode(node) {
|
||||
if (node == null || typeof node !== "object") return;
|
||||
const fields = _index.NODE_FIELDS[node.type];
|
||||
if (!fields) return;
|
||||
const keys = _index.BUILDER_KEYS[node.type];
|
||||
for (const key of keys) {
|
||||
const field = fields[key];
|
||||
if (field != null) (0, _validate.validateInternal)(field, node, key, node[key]);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=validateNode.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/validateNode.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/builders/validateNode.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"names":["_validate","require","_index","validateNode","node","fields","NODE_FIELDS","type","keys","BUILDER_KEYS","key","field","validateInternal"],"sources":["../../src/builders/validateNode.ts"],"sourcesContent":["import { validateInternal } from \"../validators/validate.ts\";\nimport type * as t from \"../index.ts\";\nimport { BUILDER_KEYS, NODE_FIELDS } from \"../index.ts\";\n\nexport default function validateNode<N extends t.Node>(node: N) {\n if (node == null || typeof node !== \"object\") return;\n const fields = NODE_FIELDS[node.type];\n if (!fields) return;\n\n // todo: because keys not in BUILDER_KEYS are not validated - this actually allows invalid nodes in some cases\n const keys = BUILDER_KEYS[node.type] as (keyof N & string)[];\n for (const key of keys) {\n const field = fields[key];\n if (field != null) validateInternal(field, node, key, node[key]);\n }\n return node;\n}\n"],"mappings":";;;;;;AAAA,IAAAA,SAAA,GAAAC,OAAA;AAEA,IAAAC,MAAA,GAAAD,OAAA;AAEe,SAASE,YAAYA,CAAmBC,IAAO,EAAE;EAC9D,IAAIA,IAAI,IAAI,IAAI,IAAI,OAAOA,IAAI,KAAK,QAAQ,EAAE;EAC9C,MAAMC,MAAM,GAAGC,kBAAW,CAACF,IAAI,CAACG,IAAI,CAAC;EACrC,IAAI,CAACF,MAAM,EAAE;EAGb,MAAMG,IAAI,GAAGC,mBAAY,CAACL,IAAI,CAACG,IAAI,CAAyB;EAC5D,KAAK,MAAMG,GAAG,IAAIF,IAAI,EAAE;IACtB,MAAMG,KAAK,GAAGN,MAAM,CAACK,GAAG,CAAC;IACzB,IAAIC,KAAK,IAAI,IAAI,EAAE,IAAAC,0BAAgB,EAACD,KAAK,EAAEP,IAAI,EAAEM,GAAG,EAAEN,IAAI,CAACM,GAAG,CAAC,CAAC;EAClE;EACA,OAAON,IAAI;AACb","ignoreList":[]}
|
||||
12
pmoapp/webapp/node_modules/@babel/types/lib/clone/clone.js
generated
vendored
Normal file
12
pmoapp/webapp/node_modules/@babel/types/lib/clone/clone.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = clone;
|
||||
var _cloneNode = require("./cloneNode.js");
|
||||
function clone(node) {
|
||||
return (0, _cloneNode.default)(node, false);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=clone.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/clone/clone.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/clone/clone.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"names":["_cloneNode","require","clone","node","cloneNode"],"sources":["../../src/clone/clone.ts"],"sourcesContent":["import cloneNode from \"./cloneNode.ts\";\nimport type * as t from \"../index.ts\";\n\n/**\n * Create a shallow clone of a `node`, including only\n * properties belonging to the node.\n * @deprecated Use t.cloneNode instead.\n */\nexport default function clone<T extends t.Node>(node: T): T {\n return cloneNode(node, /* deep */ false);\n}\n"],"mappings":";;;;;;AAAA,IAAAA,UAAA,GAAAC,OAAA;AAQe,SAASC,KAAKA,CAAmBC,IAAO,EAAK;EAC1D,OAAO,IAAAC,kBAAS,EAACD,IAAI,EAAa,KAAK,CAAC;AAC1C","ignoreList":[]}
|
||||
12
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneDeep.js
generated
vendored
Normal file
12
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneDeep.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = cloneDeep;
|
||||
var _cloneNode = require("./cloneNode.js");
|
||||
function cloneDeep(node) {
|
||||
return (0, _cloneNode.default)(node);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=cloneDeep.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneDeep.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneDeep.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"names":["_cloneNode","require","cloneDeep","node","cloneNode"],"sources":["../../src/clone/cloneDeep.ts"],"sourcesContent":["import cloneNode from \"./cloneNode.ts\";\nimport type * as t from \"../index.ts\";\n\n/**\n * Create a deep clone of a `node` and all of it's child nodes\n * including only properties belonging to the node.\n * @deprecated Use t.cloneNode instead.\n */\nexport default function cloneDeep<T extends t.Node>(node: T): T {\n return cloneNode(node);\n}\n"],"mappings":";;;;;;AAAA,IAAAA,UAAA,GAAAC,OAAA;AAQe,SAASC,SAASA,CAAmBC,IAAO,EAAK;EAC9D,OAAO,IAAAC,kBAAS,EAACD,IAAI,CAAC;AACxB","ignoreList":[]}
|
||||
12
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneDeepWithoutLoc.js
generated
vendored
Normal file
12
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneDeepWithoutLoc.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = cloneDeepWithoutLoc;
|
||||
var _cloneNode = require("./cloneNode.js");
|
||||
function cloneDeepWithoutLoc(node) {
|
||||
return (0, _cloneNode.default)(node, true, true);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=cloneDeepWithoutLoc.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneDeepWithoutLoc.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneDeepWithoutLoc.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"names":["_cloneNode","require","cloneDeepWithoutLoc","node","cloneNode"],"sources":["../../src/clone/cloneDeepWithoutLoc.ts"],"sourcesContent":["import cloneNode from \"./cloneNode.ts\";\nimport type * as t from \"../index.ts\";\n/**\n * Create a deep clone of a `node` and all of it's child nodes\n * including only properties belonging to the node.\n * excluding `_private` and location properties.\n */\nexport default function cloneDeepWithoutLoc<T extends t.Node>(node: T): T {\n return cloneNode(node, /* deep */ true, /* withoutLoc */ true);\n}\n"],"mappings":";;;;;;AAAA,IAAAA,UAAA,GAAAC,OAAA;AAOe,SAASC,mBAAmBA,CAAmBC,IAAO,EAAK;EACxE,OAAO,IAAAC,kBAAS,EAACD,IAAI,EAAa,IAAI,EAAmB,IAAI,CAAC;AAChE","ignoreList":[]}
|
||||
107
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneNode.js
generated
vendored
Normal file
107
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneNode.js
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = cloneNode;
|
||||
var _index = require("../definitions/index.js");
|
||||
var _index2 = require("../validators/generated/index.js");
|
||||
const {
|
||||
hasOwn
|
||||
} = {
|
||||
hasOwn: Function.call.bind(Object.prototype.hasOwnProperty)
|
||||
};
|
||||
function cloneIfNode(obj, deep, withoutLoc, commentsCache) {
|
||||
if (obj && typeof obj.type === "string") {
|
||||
return cloneNodeInternal(obj, deep, withoutLoc, commentsCache);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
function cloneIfNodeOrArray(obj, deep, withoutLoc, commentsCache) {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(node => cloneIfNode(node, deep, withoutLoc, commentsCache));
|
||||
}
|
||||
return cloneIfNode(obj, deep, withoutLoc, commentsCache);
|
||||
}
|
||||
function cloneNode(node, deep = true, withoutLoc = false) {
|
||||
return cloneNodeInternal(node, deep, withoutLoc, new Map());
|
||||
}
|
||||
function cloneNodeInternal(node, deep = true, withoutLoc = false, commentsCache) {
|
||||
if (!node) return node;
|
||||
const {
|
||||
type
|
||||
} = node;
|
||||
const newNode = {
|
||||
type: node.type
|
||||
};
|
||||
if ((0, _index2.isIdentifier)(node)) {
|
||||
newNode.name = node.name;
|
||||
if (hasOwn(node, "optional") && typeof node.optional === "boolean") {
|
||||
newNode.optional = node.optional;
|
||||
}
|
||||
if (hasOwn(node, "typeAnnotation")) {
|
||||
newNode.typeAnnotation = deep ? cloneIfNodeOrArray(node.typeAnnotation, true, withoutLoc, commentsCache) : node.typeAnnotation;
|
||||
}
|
||||
if (hasOwn(node, "decorators")) {
|
||||
newNode.decorators = deep ? cloneIfNodeOrArray(node.decorators, true, withoutLoc, commentsCache) : node.decorators;
|
||||
}
|
||||
} else if (!hasOwn(_index.NODE_FIELDS, type)) {
|
||||
throw new Error(`Unknown node type: "${type}"`);
|
||||
} else {
|
||||
for (const field of Object.keys(_index.NODE_FIELDS[type])) {
|
||||
if (hasOwn(node, field)) {
|
||||
if (deep) {
|
||||
newNode[field] = (0, _index2.isFile)(node) && field === "comments" ? maybeCloneComments(node.comments, deep, withoutLoc, commentsCache) : cloneIfNodeOrArray(node[field], true, withoutLoc, commentsCache);
|
||||
} else {
|
||||
newNode[field] = node[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasOwn(node, "loc")) {
|
||||
if (withoutLoc) {
|
||||
newNode.loc = null;
|
||||
} else {
|
||||
newNode.loc = node.loc;
|
||||
}
|
||||
}
|
||||
if (hasOwn(node, "leadingComments")) {
|
||||
newNode.leadingComments = maybeCloneComments(node.leadingComments, deep, withoutLoc, commentsCache);
|
||||
}
|
||||
if (hasOwn(node, "innerComments")) {
|
||||
newNode.innerComments = maybeCloneComments(node.innerComments, deep, withoutLoc, commentsCache);
|
||||
}
|
||||
if (hasOwn(node, "trailingComments")) {
|
||||
newNode.trailingComments = maybeCloneComments(node.trailingComments, deep, withoutLoc, commentsCache);
|
||||
}
|
||||
if (hasOwn(node, "extra")) {
|
||||
newNode.extra = Object.assign({}, node.extra);
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
function maybeCloneComments(comments, deep, withoutLoc, commentsCache) {
|
||||
if (!comments || !deep) {
|
||||
return comments;
|
||||
}
|
||||
return comments.map(comment => {
|
||||
const cache = commentsCache.get(comment);
|
||||
if (cache) return cache;
|
||||
const {
|
||||
type,
|
||||
value,
|
||||
loc
|
||||
} = comment;
|
||||
const ret = {
|
||||
type,
|
||||
value,
|
||||
loc
|
||||
};
|
||||
if (withoutLoc) {
|
||||
ret.loc = null;
|
||||
}
|
||||
commentsCache.set(comment, ret);
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
//# sourceMappingURL=cloneNode.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneNode.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneNode.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
12
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneWithoutLoc.js
generated
vendored
Normal file
12
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneWithoutLoc.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = cloneWithoutLoc;
|
||||
var _cloneNode = require("./cloneNode.js");
|
||||
function cloneWithoutLoc(node) {
|
||||
return (0, _cloneNode.default)(node, false, true);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=cloneWithoutLoc.js.map
|
||||
1
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneWithoutLoc.js.map
generated
vendored
Normal file
1
pmoapp/webapp/node_modules/@babel/types/lib/clone/cloneWithoutLoc.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"names":["_cloneNode","require","cloneWithoutLoc","node","cloneNode"],"sources":["../../src/clone/cloneWithoutLoc.ts"],"sourcesContent":["import cloneNode from \"./cloneNode.ts\";\nimport type * as t from \"../index.ts\";\n\n/**\n * Create a shallow clone of a `node` excluding `_private` and location properties.\n */\nexport default function cloneWithoutLoc<T extends t.Node>(node: T): T {\n return cloneNode(node, /* deep */ false, /* withoutLoc */ true);\n}\n"],"mappings":";;;;;;AAAA,IAAAA,UAAA,GAAAC,OAAA;AAMe,SAASC,eAAeA,CAAmBC,IAAO,EAAK;EACpE,OAAO,IAAAC,kBAAS,EAACD,IAAI,EAAa,KAAK,EAAmB,IAAI,CAAC;AACjE","ignoreList":[]}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user