001/* 002 * SPDX-License-Identifier: Apache-2.0 003 * 004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package dev.enola.common.linereader; 019 020import static java.nio.charset.StandardCharsets.UTF_8; 021 022import com.google.common.collect.ImmutableMap; 023 024import org.jspecify.annotations.Nullable; 025 026import java.io.*; 027import java.nio.charset.Charset; 028import java.util.List; 029 030public class TestIO implements IO { 031 032 static final Charset CHARSET = UTF_8; 033 034 private final BufferedReader inputReader; 035 private final ByteArrayInputStream input; 036 private final ByteArrayOutputStream output = new ByteArrayOutputStream(); 037 private final ExecutionContext ctx; 038 039 public TestIO(List<String> lines) { 040 var text = String.join("\n", lines); 041 var bytes = text.getBytes(CHARSET); 042 this.input = new ByteArrayInputStream(bytes); 043 this.inputReader = new BufferedReader(new InputStreamReader(input, CHARSET)); 044 045 this.ctx = 046 new ExecutionContext() { 047 @Override 048 public ImmutableMap<String, String> environment() { 049 return ImmutableMap.of(); 050 } 051 052 @Override 053 public InputStream input() { 054 return input; 055 } 056 057 @Override 058 public OutputStream output() { 059 return output; 060 } 061 062 @Override 063 public OutputStream error() { 064 return output; 065 } 066 067 @Override 068 public Charset inputCharset() { 069 return CHARSET; 070 } 071 072 @Override 073 public Charset outputCharset() { 074 return CHARSET; 075 } 076 077 @Override 078 public Charset errorCharset() { 079 return CHARSET; 080 } 081 }; 082 } 083 084 @Override 085 public ExecutionContext ctx() { 086 return ctx; 087 } 088 089 @Override 090 public @Nullable String readLine() { 091 // if (input.available() < 1) return null; 092 try { 093 if (!inputReader.ready()) return null; 094 return inputReader.readLine(); 095 } catch (IOException ex) { 096 throw new UncheckedIOException(ex); 097 } 098 } 099 100 @Override 101 public @Nullable String readLine(String prompt) { 102 printf(prompt); 103 return readLine(); 104 } 105 106 @Override 107 public void printf(String format, Object... args) { 108 output.writeBytes(String.format(format, args).getBytes(ctx.outputCharset())); 109 } 110 111 // This signature is misleading, because it makes it appear as if we were tracking "line", 112 // whereas of course this is actually not so; any notion of print-by-line is meaningless 113 // and lost as soon as any printf() contains new line characters. 114 // public List<String> getOutput() { 115 // return output.toString(ctx.outputCharset()).lines().toList(); } 116 117 public String getOutput() { 118 return output.toString(ctx.outputCharset()); 119 } 120}