aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/io
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-26 20:04:02 +0900
committernsfisis <nsfisis@gmail.com>2026-05-26 20:04:02 +0900
commitf411daceacad66e0bd774fda7d3c5ef8533cc55c (patch)
treeeefb065e4d676a3f7031ca49bab21c773b00b134 /crates/shirabe/src/io
parent1921f173ea219cb4b25847294d2d3fa465550fbb (diff)
downloadphp-shirabe-f411daceacad66e0bd774fda7d3c5ef8533cc55c.tar.gz
php-shirabe-f411daceacad66e0bd774fda7d3c5ef8533cc55c.tar.zst
php-shirabe-f411daceacad66e0bd774fda7d3c5ef8533cc55c.zip
refactor(io): share IOInterface via Rc<RefCell<dyn _>> handle
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/io')
-rw-r--r--crates/shirabe/src/io/buffer_io.rs52
-rw-r--r--crates/shirabe/src/io/console_io.rs63
-rw-r--r--crates/shirabe/src/io/io_interface.rs215
-rw-r--r--crates/shirabe/src/io/null_io.rs59
4 files changed, 268 insertions, 121 deletions
diff --git a/crates/shirabe/src/io/buffer_io.rs b/crates/shirabe/src/io/buffer_io.rs
index 867eb0e..ed87b71 100644
--- a/crates/shirabe/src/io/buffer_io.rs
+++ b/crates/shirabe/src/io/buffer_io.rs
@@ -129,39 +129,9 @@ impl BufferIO {
}
}
-// TODO(phase-b): PHP `class BufferIO extends ConsoleIO` — delegate all IOInterface,
-// LoggerInterface, and BaseIO methods to `self.inner` (ConsoleIO).
-impl shirabe_external_packages::psr::log::LoggerInterface for BufferIO {
- fn emergency(&self, message: &str, context: &[(&str, &str)]) {
- self.inner.emergency(message, context)
- }
- fn alert(&self, message: &str, context: &[(&str, &str)]) {
- self.inner.alert(message, context)
- }
- fn critical(&self, message: &str, context: &[(&str, &str)]) {
- self.inner.critical(message, context)
- }
- fn error(&self, message: &str, context: &[(&str, &str)]) {
- self.inner.error(message, context)
- }
- fn warning(&self, message: &str, context: &[(&str, &str)]) {
- self.inner.warning(message, context)
- }
- fn notice(&self, message: &str, context: &[(&str, &str)]) {
- self.inner.notice(message, context)
- }
- fn info(&self, message: &str, context: &[(&str, &str)]) {
- self.inner.info(message, context)
- }
- fn debug(&self, message: &str, context: &[(&str, &str)]) {
- self.inner.debug(message, context)
- }
- fn log(&self, level: &str, message: &str, context: &[(&str, &str)]) {
- self.inner.log(level, message, context)
- }
-}
-
-impl crate::io::IOInterface for BufferIO {
+// TODO(phase-b): PHP `class BufferIO extends ConsoleIO` — delegate all
+// IOInterface and BaseIO methods to `self.inner` (ConsoleIO).
+impl crate::io::IOInterfaceImmutable for BufferIO {
fn is_interactive(&self) -> bool {
self.inner.is_interactive()
}
@@ -247,6 +217,20 @@ impl crate::io::IOInterface for BufferIO {
) -> indexmap::IndexMap<String, Option<String>> {
self.inner.get_authentication(repository_name)
}
+ fn error(&self, message: &str, context: &[(&str, &str)]) {
+ self.inner.error(message, context)
+ }
+
+ fn warning(&self, message: &str, context: &[(&str, &str)]) {
+ self.inner.warning(message, context)
+ }
+
+ fn debug(&self, message: &str, context: &[(&str, &str)]) {
+ self.inner.debug(message, context)
+ }
+}
+
+impl crate::io::IOInterfaceMutable for BufferIO {
fn set_authentication(
&mut self,
repository_name: String,
@@ -261,6 +245,8 @@ impl crate::io::IOInterface for BufferIO {
}
}
+impl crate::io::IOInterface for BufferIO {}
+
impl crate::io::BaseIO for BufferIO {
fn authentications(
&self,
diff --git a/crates/shirabe/src/io/console_io.rs b/crates/shirabe/src/io/console_io.rs
index b4bae47..2b1c334 100644
--- a/crates/shirabe/src/io/console_io.rs
+++ b/crates/shirabe/src/io/console_io.rs
@@ -5,7 +5,6 @@ use crate::io::io_interface;
use indexmap::IndexMap;
use indexmap::indexmap;
use shirabe_external_packages::composer::pcre::Preg;
-use shirabe_external_packages::psr::log::LoggerInterface;
use shirabe_external_packages::symfony::component::console::helper::HelperSet;
use shirabe_external_packages::symfony::component::console::helper::ProgressBar;
use shirabe_external_packages::symfony::component::console::helper::Table;
@@ -25,6 +24,8 @@ use std::cell::RefCell;
use crate::io::BaseIO;
use crate::io::IOInterface;
+use crate::io::IOInterfaceImmutable;
+use crate::io::IOInterfaceMutable;
use crate::question::StrictConfirmationQuestion;
use crate::util::Silencer;
@@ -378,49 +379,7 @@ impl ConsoleIO {
}
}
-impl LoggerInterface for ConsoleIO {
- // TODO(phase-b): BaseIO's emergency/alert/.../log take PhpMixed and
- // IndexMap<String, Box<PhpMixed>> while LoggerInterface takes &str and
- // &[(&str, &str)]. Delegation requires reconciling signatures; for now,
- // mirror NullIO and panic via todo!().
- fn emergency(&self, _message: &str, _context: &[(&str, &str)]) {
- todo!()
- }
-
- fn alert(&self, _message: &str, _context: &[(&str, &str)]) {
- todo!()
- }
-
- fn critical(&self, _message: &str, _context: &[(&str, &str)]) {
- todo!()
- }
-
- fn error(&self, _message: &str, _context: &[(&str, &str)]) {
- todo!()
- }
-
- fn warning(&self, _message: &str, _context: &[(&str, &str)]) {
- todo!()
- }
-
- fn notice(&self, _message: &str, _context: &[(&str, &str)]) {
- todo!()
- }
-
- fn info(&self, _message: &str, _context: &[(&str, &str)]) {
- todo!()
- }
-
- fn debug(&self, _message: &str, _context: &[(&str, &str)]) {
- todo!()
- }
-
- fn log(&self, _level: &str, _message: &str, _context: &[(&str, &str)]) {
- todo!()
- }
-}
-
-impl IOInterface for ConsoleIO {
+impl IOInterfaceImmutable for ConsoleIO {
fn is_interactive(&self) -> bool {
self.input.is_interactive()
}
@@ -690,6 +649,20 @@ impl IOInterface for ConsoleIO {
<Self as BaseIO>::get_authentication(self, repository_name)
}
+ fn error(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
+ }
+
+ fn warning(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
+ }
+
+ fn debug(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
+ }
+}
+
+impl IOInterfaceMutable for ConsoleIO {
fn set_authentication(
&mut self,
repository_name: String,
@@ -704,6 +677,8 @@ impl IOInterface for ConsoleIO {
}
}
+impl IOInterface for ConsoleIO {}
+
impl BaseIO for ConsoleIO {
fn authentications(
&self,
diff --git a/crates/shirabe/src/io/io_interface.rs b/crates/shirabe/src/io/io_interface.rs
index 1d97700..aac1488 100644
--- a/crates/shirabe/src/io/io_interface.rs
+++ b/crates/shirabe/src/io/io_interface.rs
@@ -2,8 +2,9 @@
use crate::config::Config;
use indexmap::IndexMap;
-use shirabe_external_packages::psr::log::LoggerInterface;
use shirabe_php_shim::PhpMixed;
+use std::cell::RefCell;
+use std::rc::Rc;
pub const QUIET: i64 = 1;
pub const NORMAL: i64 = 2;
@@ -11,7 +12,18 @@ pub const VERBOSE: i64 = 4;
pub const VERY_VERBOSE: i64 = 8;
pub const DEBUG: i64 = 16;
-pub trait IOInterface: LoggerInterface + std::fmt::Debug {
+// In PHP this is `IOInterface extends LoggerInterface`. Shirabe does not
+// integrate with the PHP runtime, so there is no need for a separate
+// `LoggerInterface` entity on the Rust side: the LoggerInterface methods that
+// Composer actually invokes through an IO are folded directly into this trait
+// (each is annotated as originating from LoggerInterface).
+//
+// On the Rust side the interface is split into an immutable part (`&self`
+// methods, below) and a mutable part (`IOInterfaceMutable`, the `&mut self`
+// methods). The shared handle `Rc<RefCell<dyn IOInterface>>` implements only
+// `IOInterfaceImmutable`, so the mutating methods are reachable only via
+// `io.borrow_mut()` — enforced at compile time rather than at runtime.
+pub trait IOInterfaceImmutable: std::fmt::Debug {
fn is_interactive(&self) -> bool;
fn is_verbose(&self) -> bool;
@@ -112,6 +124,19 @@ pub trait IOInterface: LoggerInterface + std::fmt::Debug {
fn get_authentication(&self, repository_name: &str) -> IndexMap<String, Option<String>>;
+ // From PHP `LoggerInterface` (which `IOInterface` extends). Only the
+ // variants Composer actually calls through an IO are kept.
+ fn error(&self, message: &str, context: &[(&str, &str)]);
+
+ fn warning(&self, message: &str, context: &[(&str, &str)]);
+
+ fn debug(&self, message: &str, context: &[(&str, &str)]);
+}
+
+// The `&mut self` part of PHP `IOInterface`. The shared handle does NOT
+// implement this trait, so these methods can only be reached through
+// `io.borrow_mut()`.
+pub trait IOInterfaceMutable {
fn set_authentication(
&mut self,
repository_name: String,
@@ -120,8 +145,190 @@ pub trait IOInterface: LoggerInterface + std::fmt::Debug {
);
fn load_configuration(&mut self, config: &mut Config) -> anyhow::Result<()>;
+}
+
+// PHP `IOInterface`. This is the type used for the shared trait object
+// `dyn IOInterface`; its vtable carries both the immutable and mutable methods.
+pub trait IOInterface: IOInterfaceImmutable + IOInterfaceMutable {}
+
+// Shared-ownership handle for a PHP IO instance (reference semantics). It
+// exposes only the immutable surface; mutating methods (`set_authentication`,
+// `load_configuration`) are reached via `io.borrow_mut()`. Because the handle
+// does not implement `IOInterfaceMutable`, calling those directly on the handle
+// is a compile error rather than a runtime panic.
+impl IOInterfaceImmutable for Rc<RefCell<dyn IOInterface>> {
+ fn is_interactive(&self) -> bool {
+ self.borrow().is_interactive()
+ }
+
+ fn is_verbose(&self) -> bool {
+ self.borrow().is_verbose()
+ }
+
+ fn is_very_verbose(&self) -> bool {
+ self.borrow().is_very_verbose()
+ }
+
+ fn is_debug(&self) -> bool {
+ self.borrow().is_debug()
+ }
+
+ fn is_decorated(&self) -> bool {
+ self.borrow().is_decorated()
+ }
+
+ fn write(&self, message: &str) {
+ self.borrow().write(message)
+ }
+
+ fn write2(&self, message: &str, newline: bool) {
+ self.borrow().write2(message, newline)
+ }
+
+ fn write_no_newline(&self, message: &str) {
+ self.borrow().write_no_newline(message)
+ }
+
+ fn write3(&self, message: &str, newline: bool, verbosity: i64) {
+ self.borrow().write3(message, newline, verbosity)
+ }
+
+ fn write_error(&self, message: &str) {
+ self.borrow().write_error(message)
+ }
+
+ fn write_error2(&self, message: &str, newline: bool) {
+ self.borrow().write_error2(message, newline)
+ }
+
+ fn write_error_no_newline(&self, message: &str) {
+ self.borrow().write_error_no_newline(message)
+ }
+
+ fn write_error3(&self, message: &str, newline: bool, verbosity: i64) {
+ self.borrow().write_error3(message, newline, verbosity)
+ }
+
+ fn write_raw(&self, message: &str) {
+ self.borrow().write_raw(message)
+ }
+
+ fn write_raw2(&self, message: &str, newline: bool) {
+ self.borrow().write_raw2(message, newline)
+ }
+
+ fn write_raw3(&self, message: &str, newline: bool, verbosity: i64) {
+ self.borrow().write_raw3(message, newline, verbosity)
+ }
+
+ fn write_error_raw(&self, message: &str) {
+ self.borrow().write_error_raw(message)
+ }
+
+ fn write_error_raw2(&self, message: &str, newline: bool) {
+ self.borrow().write_error_raw2(message, newline)
+ }
+
+ fn write_error_raw3(&self, message: &str, newline: bool, verbosity: i64) {
+ self.borrow().write_error_raw3(message, newline, verbosity)
+ }
+
+ fn overwrite(&self, message: &str) {
+ self.borrow().overwrite(message)
+ }
+
+ fn overwrite2(&self, message: &str, newline: bool) {
+ self.borrow().overwrite2(message, newline)
+ }
+
+ fn overwrite3(&self, message: &str, newline: bool, size: Option<i64>) {
+ self.borrow().overwrite3(message, newline, size)
+ }
+
+ fn overwrite4(&self, message: &str, newline: bool, size: Option<i64>, verbosity: i64) {
+ self.borrow().overwrite4(message, newline, size, verbosity)
+ }
+
+ fn overwrite_error(&self, message: &str) {
+ self.borrow().overwrite_error(message)
+ }
+
+ fn overwrite_error2(&self, message: &str, newline: bool) {
+ self.borrow().overwrite_error2(message, newline)
+ }
+
+ fn overwrite_error3(&self, message: &str, newline: bool, size: Option<i64>) {
+ self.borrow().overwrite_error3(message, newline, size)
+ }
+
+ fn overwrite_error4(&self, message: &str, newline: bool, size: Option<i64>, verbosity: i64) {
+ self.borrow()
+ .overwrite_error4(message, newline, size, verbosity)
+ }
+
+ fn ask(&self, question: String, default: PhpMixed) -> PhpMixed {
+ self.borrow().ask(question, default)
+ }
+
+ fn ask_confirmation(&self, question: String, default: bool) -> bool {
+ self.borrow().ask_confirmation(question, default)
+ }
+
+ fn ask_and_validate(
+ &self,
+ question: String,
+ validator: Box<dyn Fn(PhpMixed) -> PhpMixed>,
+ attempts: Option<i64>,
+ default: PhpMixed,
+ ) -> PhpMixed {
+ self.borrow()
+ .ask_and_validate(question, validator, attempts, default)
+ }
+
+ fn ask_and_hide_answer(&self, question: String) -> Option<String> {
+ self.borrow().ask_and_hide_answer(question)
+ }
+
+ fn select(
+ &self,
+ question: String,
+ choices: Vec<String>,
+ default: PhpMixed,
+ attempts: PhpMixed,
+ error_message: String,
+ multiselect: bool,
+ ) -> PhpMixed {
+ self.borrow().select(
+ question,
+ choices,
+ default,
+ attempts,
+ error_message,
+ multiselect,
+ )
+ }
+
+ fn get_authentications(&self) -> IndexMap<String, IndexMap<String, Option<String>>> {
+ self.borrow().get_authentications()
+ }
+
+ fn has_authentication(&self, repository_name: &str) -> bool {
+ self.borrow().has_authentication(repository_name)
+ }
+
+ fn get_authentication(&self, repository_name: &str) -> IndexMap<String, Option<String>> {
+ self.borrow().get_authentication(repository_name)
+ }
+
+ fn error(&self, message: &str, context: &[(&str, &str)]) {
+ self.borrow().error(message, context)
+ }
+
+ fn warning(&self, message: &str, context: &[(&str, &str)]) {
+ self.borrow().warning(message, context)
+ }
- fn clone_box(&self) -> Box<dyn IOInterface> {
- todo!()
+ fn debug(&self, message: &str, context: &[(&str, &str)]) {
+ self.borrow().debug(message, context)
}
}
diff --git a/crates/shirabe/src/io/null_io.rs b/crates/shirabe/src/io/null_io.rs
index e3cfdcf..b0e11f1 100644
--- a/crates/shirabe/src/io/null_io.rs
+++ b/crates/shirabe/src/io/null_io.rs
@@ -2,7 +2,8 @@
use crate::io::BaseIO;
use crate::io::IOInterface;
-use shirabe_external_packages::psr::log::LoggerInterface;
+use crate::io::IOInterfaceImmutable;
+use crate::io::IOInterfaceMutable;
use shirabe_php_shim::PhpMixed;
#[derive(Debug)]
@@ -18,7 +19,7 @@ impl NullIO {
}
}
-impl IOInterface for NullIO {
+impl IOInterfaceImmutable for NullIO {
fn is_interactive(&self) -> bool {
false
}
@@ -109,6 +110,20 @@ impl IOInterface for NullIO {
<Self as BaseIO>::get_authentication(self, repository_name)
}
+ fn error(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
+ }
+
+ fn warning(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
+ }
+
+ fn debug(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
+ }
+}
+
+impl IOInterfaceMutable for NullIO {
fn set_authentication(
&mut self,
repository_name: String,
@@ -123,6 +138,8 @@ impl IOInterface for NullIO {
}
}
+impl IOInterface for NullIO {}
+
impl BaseIO for NullIO {
fn authentications(
&self,
@@ -136,41 +153,3 @@ impl BaseIO for NullIO {
&mut self.authentications
}
}
-
-impl LoggerInterface for NullIO {
- fn emergency(&self, message: &str, context: &[(&str, &str)]) {
- todo!()
- }
-
- fn alert(&self, message: &str, context: &[(&str, &str)]) {
- todo!()
- }
-
- fn critical(&self, message: &str, context: &[(&str, &str)]) {
- todo!()
- }
-
- fn error(&self, message: &str, context: &[(&str, &str)]) {
- todo!()
- }
-
- fn warning(&self, message: &str, context: &[(&str, &str)]) {
- todo!()
- }
-
- fn notice(&self, message: &str, context: &[(&str, &str)]) {
- todo!()
- }
-
- fn info(&self, message: &str, context: &[(&str, &str)]) {
- todo!()
- }
-
- fn debug(&self, message: &str, context: &[(&str, &str)]) {
- todo!()
- }
-
- fn log(&self, level: &str, message: &str, context: &[(&str, &str)]) {
- todo!()
- }
-}