Skip to content

Conversation

@Stivo182
Copy link
Contributor

@Stivo182 Stivo182 commented Dec 18, 2025

Добавляет поддержку потока в ЗаписьТекста (#1532)

Summary by CodeRabbit

  • New Features

    • Unified support for writing to either files or external streams with configurable encoding and BOM handling
    • Line delimiter customization available for both file- and stream-based output
    • New public constructor overloads to initialize writers from streams
  • Bug Fixes

    • Improved validation and clearer errors for closed or non-writable streams
  • Tests

    • Expanded test suite covering file/stream writes, encodings, BOM, delimiters, and interop scenarios

✏️ Tip: You can customize this high-level summary in your review settings.


Note

Extends TextWriter to write to streams with configurable encoding/BOM and line delimiters, adds errors for closed/read-only streams, and updates tests accordingly.

  • Standard Library: Text
    • TextWriteImpl:
      • Add stream-based writing via IValue/IStreamWrapper in Open and constructors (file or stream).
      • Support encoding resolution for files/streams, including UTF-8 BOM control (ResolveEncodingForFile/Stream).
      • Handle line delimiters and EOL conversion for both files and streams.
      • Lazy initialize StreamWriter for streams (PrepareForWriting), with immediate auto-flush.
      • Validate stream state: throw on closed or read-only streams; allow re-opening between file and stream.
  • Exceptions
    • Add RuntimeException.ClosedStream() and RuntimeException.NonWritableStream() factory methods.
  • Tests
    • Add tests for writing to memory streams, custom encodings/BOM, delimiter behavior (file and stream), re-opening scenarios, and error cases (closed/read-only streams).

Written by Cursor Bugbot for commit cebce69. This will update automatically on new commits. Configure here.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Walkthrough

Adds stream-backed writing to TextWrite: new stream-aware constructors and an Open overload that accepts either a file path or a stream, deferred writer initialization for streams, encoding/BOM resolution for both targets, two new RuntimeException factory methods for stream errors, and expanded tests covering files and streams.

Changes

Cohort / File(s) Summary
Exception handling
src/OneScript.Core/Exceptions/RuntimeException.cs
Added two new static factory methods: ClosedStream() and NonWritableStream() to represent errors when accessing a closed stream or writing to a non-writable stream.
Text writer implementation
src/OneScript.StandardLibrary/Text/TextWriteImpl.cs
Added stream support: new TextWriteImpl(IValue stream, IValue encoding, IValue writeBom) constructor; changed Open(IValue fileOrStream, IValue encoding = null, string lineDelimiter = null, IValue param4 = null, IValue param5 = null) to accept file or stream and branch to OpenFile() / OpenStream(); deferred writer creation via PrepareForWriting(); added _streamWrapper and _encoding state, ResolveEncodingForFile() and ResolveEncodingForStream() helpers, and validation helpers ThrowIfClosedStream() / ThrowIfNonWritableStream().
Tests
tests/text-write.os
Expanded and reorganized tests: new sections for stream tests ("Работа с потоком") and file+stream interop; tests for memory stream writing, encodings (UTF‑8 w/wo BOM, ANSI), BOM handling, closed/non-writable stream errors, reopen scenarios, delimiter handling, and helper procedures ЭкранироватьПереносыСтрок and ПодробноеСравнениеСтрок.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Files/areas needing extra attention:

  • TextWriteImpl branching and state handling for file vs stream targets (constructor overloads, Open, PrepareForWriting).
  • Encoding/BOM resolution correctness (ResolveEncodingForFile, ResolveEncodingForStream) and edge cases for UTF‑8 BOM.
  • Stream validation methods and mapping to the new RuntimeException factories.
  • Tests in tests/text-write.os for coverage completeness and assumptions about existing APIs.

Possibly related PRs

Suggested reviewers

  • EvilBeaver

Poem

"Я кролик-писарь, хвостик взмыл,
Пишу в файлы, в потоки милы.
Кодом шуршу, BOM прячу в ряд,
Закрытый поток — не пройдёт взгляд.
Хоп — и строки текут вперёд!" 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Поддержка потока в ЗаписьТекста' directly and clearly describes the main change: adding stream support to the TextWrite functionality, which is the core purpose of all modifications.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (7)
tests/text-write.os (7)

293-300: Stream seek should occur before creating the reader.

The ЧтениеТекста is created on line 293, but Поток.Перейти(0, ПозицияВПотоке.Начало) is called on line 294 afterward. Depending on how ЧтениеТекста initializes, it may read from the current (end) position. Move the seek before creating the reader:

Suggested fix
 	// Тогда
+	Поток.Перейти(0, ПозицияВПотоке.Начало);
 	ЧтениеТекста = Новый ЧтениеТекста(Поток);
-	Поток.Перейти(0, ПозицияВПотоке.Начало);

Additionally, the try-catch block (lines 296-300) only rethrows the exception without any cleanup—it can be removed for clarity.


314-323: Same stream seek ordering issue.

The seek on line 316 should occur before creating ЧтениеТекста on line 315 to ensure reading from the beginning. The try-catch block is also redundant here.


337-346: Same stream seek ordering issue as above.

Move Поток.Перейти(0, ПозицияВПотоке.Начало) before creating ЧтениеТекста.


361-369: Same stream seek ordering issue.

Move the seek before reader creation.


428-434: Stream seek ordering.

Line 431 seeks after reader creation at line 429. While this may work if ЧтениеТекста doesn't buffer on construction, it's safer to seek first.


458-464: Stream seek ordering.

Same issue—seek at line 461 should precede reader creation at line 459.


519-525: Stream seek ordering in helper.

Move line 523 (Поток.Перейти(0, ПозицияВПотоке.Начало)) before line 521 where Чтение.Открыть is called, to ensure reading starts from the beginning.

Suggested fix
 	// Тогда
+	Поток.Перейти(0, ПозицияВПотоке.Начало);
 	Чтение = Новый ЧтениеТекста;
 	Чтение.Открыть(Поток, "UTF-8", Символы.ПС, РазделительСтрокВФайле);
-
-	Поток.Перейти(0, ПозицияВПотоке.Начало);
 	
 	ПроверяемыйТекст = Чтение.Прочитать();
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f46f06 and cebce69.

📒 Files selected for processing (3)
  • src/OneScript.Core/Exceptions/RuntimeException.cs (1 hunks)
  • src/OneScript.StandardLibrary/Text/TextWriteImpl.cs (4 hunks)
  • tests/text-write.os (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.cs

📄 CodeRabbit inference engine (.cursor/rules/langversion.mdc)

Do not use C# language features that are not available in C# 8 when generating code for projects using .NET 8.0 with LangVersion 8

Files:

  • src/OneScript.Core/Exceptions/RuntimeException.cs
  • src/OneScript.StandardLibrary/Text/TextWriteImpl.cs
🧠 Learnings (2)
📚 Learning: 2025-12-18T16:13:05.448Z
Learnt from: Mr-Rm
Repo: EvilBeaver/OneScript PR: 1636
File: src/OneScript.StandardLibrary/StringOperations.cs:155-157
Timestamp: 2025-12-18T16:13:05.448Z
Learning: Guideline: In OneScript, when a method parameter has a concrete type (e.g., string) and is called from scripts with omitted arguments, the runtime passes an empty string "" rather than null. Direct (non-script) calls to the method may still pass null, so implement defensive null checks for these parameters in public methods that can be called from scripts. Treat empty string as a legitimate value for script calls, and explicitly handle null for direct calls (e.g., fail fast, throw ArgumentNullException, or normalize to "" where appropriate). This should apply to all C# methods that may be invoked from OneScript with optional string parameters across the codebase.

Applied to files:

  • src/OneScript.Core/Exceptions/RuntimeException.cs
  • src/OneScript.StandardLibrary/Text/TextWriteImpl.cs
📚 Learning: 2025-12-01T15:13:19.064Z
Learnt from: CR
Repo: EvilBeaver/OneScript PR: 0
File: .cursor/rules/runbsltests.mdc:0-0
Timestamp: 2025-12-01T15:13:19.064Z
Learning: Ignore errors from http.os test file and the test named 'Тест_ДолженПроверитьЧтоПеремещениеФайлаРаботаетПоHTTP' when reviewing BSL test results

Applied to files:

  • tests/text-write.os
🔇 Additional comments (16)
tests/text-write.os (1)

22-34: Good test coverage for new stream functionality.

The new test sections comprehensively cover stream writing scenarios including encoding variations, BOM handling, error cases (closed stream, read-only stream), and switching between file and stream modes.

src/OneScript.Core/Exceptions/RuntimeException.cs (1)

194-206: LGTM!

The new ClosedStream() and NonWritableStream() factory methods follow the established pattern, provide clear bilingual messages, and align with the stream validation requirements in TextWriteImpl.

src/OneScript.StandardLibrary/Text/TextWriteImpl.cs (14)

25-29: LGTM!

The new _streamWrapper and _encoding fields appropriately support deferred writer initialization for stream-based writing.


46-49: LGTM!

The new stream constructor properly delegates to Open with the stream, encoding, and BOM flag parameters.


108-115: LGTM!

The Write method now properly calls PrepareForWriting() before writing, ensuring the writer is initialized and stream state is validated for both file and stream targets.


123-132: LGTM!

WriteLine now properly delegates to Write, ensuring stream validation and writer preparation occur consistently.


140-149: LGTM!

Setting _streamWrapper = null without disposing the underlying stream is correct since the StreamWriter is created with leaveOpen: true (line 158), allowing the stream to be reused for reading.


151-163: LGTM!

The PrepareForWriting method properly validates stream state before creating the writer, and correctly uses leaveOpen: true to allow stream reuse after writing.


165-176: LGTM!

OpenFile properly cleans up previous state via Dispose(), sets _streamWrapper = null to indicate file mode, and creates the StreamWriter directly for file-based writing.


178-186: LGTM!

OpenStream correctly stores the stream wrapper and encoding for deferred writer creation in PrepareForWriting, enabling proper stream state validation at write time.


188-202: LGTM!

The logic correctly handles the UTF-8 BOM edge case: when appending to an existing file, it uses UTF8Encoding(false) to prevent writing a duplicate BOM.


204-214: LGTM!

Stream encoding resolution properly respects the writeBom flag for both default UTF-8 and specified encodings.


216-224: LGTM!

The property pattern stream is { CanWrite: false, CanRead: false } is C# 8 compatible and correctly identifies a closed stream.


226-230: LGTM!

Simple and effective validation using IStreamWrapper.IsReadOnly to prevent writes to read-only streams.


255-261: LGTM!

The script constructor factory properly mirrors the updated Open signature, enabling both file and stream initialization from scripts.


75-95: No action required. The ConvertValueStrict<T> method handles null parameters gracefully by returning default values (null for strings, false for booleans) rather than throwing exceptions. The parameter reinterpretation based on whether the first argument is a stream or file is intentional, with each path receiving semantically appropriate parameters for its use case.

Likely an incorrect or invalid review comment.

@EvilBeaver
Copy link
Owner

@cursor review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no bugs!



private void ThrowIfNonWritableStream()
{
if (_streamWrapper != null && _streamWrapper.IsReadOnly)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

На днях починили поток только для чтения, чтобы сам выбрасывал исключения af962b1

Наверное имеет смысл обновиться с develop и убрать эту проверку, чтобы не дублировать тексты исключения

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Без этого возникнет исключение с родным сообщением от .NET, поскольку запись будет происходить напрямую в Stream из StreamWriter. А с этим подходом текст исключения будет таким же, как в 1С.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не очень понял... Мы используем тут не родной стрим .net, а наш враппер стримов. В коммите af962b1 сделана проверка на ReadOnly в наших стримах с текстом исключения, как в 1С.

Или я не прав?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

На данный момент просто реализовано так, что запись (Записать, ЗаписатьСтроку) идёт не через врапер потоков, а через StreamWriter, который работает напрямую с потоком .NET из врапера. Поэтому исключения будут дотнетовские.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cebce69 and 96cc743.

📒 Files selected for processing (1)
  • tests/text-write.os (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: EvilBeaver
Repo: EvilBeaver/OneScript PR: 0
File: :0-0
Timestamp: 2024-08-05T18:27:08.563Z
Learning: For each new pull request in the EvilBeaver/OneScript repository, include a poem in Russian.
📚 Learning: 2025-12-01T15:13:19.064Z
Learnt from: CR
Repo: EvilBeaver/OneScript PR: 0
File: .cursor/rules/runbsltests.mdc:0-0
Timestamp: 2025-12-01T15:13:19.064Z
Learning: Ignore errors from http.os test file and the test named 'Тест_ДолженПроверитьЧтоПеремещениеФайлаРаботаетПоHTTP' when reviewing BSL test results

Applied to files:

  • tests/text-write.os
🔇 Additional comments (7)
tests/text-write.os (7)

9-36: LGTM! Clear test organization.

The test structure is well-organized with clear sections for file operations, stream operations, and file/stream interoperability. Test names are descriptive and follow the existing convention.


282-355: LGTM! Comprehensive stream operation tests.

The stream tests provide good coverage:

  • Basic writing to memory stream
  • Custom encoding handling
  • BOM marker support
  • Setting stream via Open method

All tests correctly verify the written content and use appropriate encoding/BOM parameters.


357-366: Test validates deferred initialization pattern.

This test confirms that constructing TextWrite with a closed stream doesn't throw immediately, which aligns with the lazy initialization pattern mentioned in the PR description ("Ленивое создание StreamWriter для потоков"). The actual exception is tested separately in line 368-379 when attempting to write.


368-393: LGTM! Error handling tests are correct.

Both tests properly verify that:

  • Writing to a closed stream throws "Ошибка обращения к закрытому потоку"
  • Writing to a read-only stream throws "Попытка записи в поток не поддерживающий запись"

The use of ПроверитьКодСОшибкой is appropriate for exception verification.


395-453: LGTM! Interop tests validate reopening behavior.

Both tests correctly verify that TextWrite can be reopened between file and stream targets, with each destination retaining its respective content. The tests include proper cleanup of temporary files.


471-490: LGTM! File delimiter test helper is correct.

The helper properly tests delimiter handling by:

  • Writing text with specified in-memory and file delimiters
  • Reading it back with matching delimiters
  • Comparing the results

The parameter order matches the expected signature: Открыть(ИмяФайла, encoding, lineSeparator, BOM, fileSeparator).


515-532: LGTM! Comparison helpers improve test diagnostics.

The helpers correctly escape line breaks (CR and LF) for clearer error messages when delimiter tests fail. This makes debugging much easier by showing exactly which line endings are present.

@Stivo182 Stivo182 requested a review from EvilBeaver December 19, 2025 20:37
@EvilBeaver EvilBeaver merged commit 0a18df3 into EvilBeaver:develop Dec 20, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants