mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-09-30 01:52:29 +00:00
* feat: rename working_dir to vault_dir and update documentation - Rename working_dir to vault_dir across the application - Update documentation to reflect vault_dir instead of working_dir - Change FileFrontMatter title field to name field - Update .gitignore to include vault directory - Modify file path descriptions to reference vault instead of working_dir - Update related configuration and property names accordingly * refactor(steps): rename working_path to vault_path in CRUD operations - Rename parameter from `working_path` to `vault_path` in `resolve_path` function - Update all usages in append, edit, read, and write steps to use `self.vault_path` - Update documentation comments to reflect the new parameter name - Update docstring in read.py to mention `vault_dir` instead of `vault` test(chunked_file_parser): update frontmatter field from title to name - Change frontmatter field from `title` to `name` in test cases - Update comment in background steps test to reference `vault_path` instead of `working_path` * refactor(schema): remove unused ResourceEntry import * feat(file_graph): add link scope filtering to get_inlinks/get_outlinks * feat(file-store): add scope parameter to link methods
75 lines
2.9 KiB
Python
75 lines
2.9 KiB
Python
"""Append content to the end of a file (auto-creates if missing)."""
|
|
|
|
import aiofiles
|
|
|
|
from ._file_io import NON_MD_WARNING, detect_file_encoding, gate_md, resolve_path
|
|
from ..base_step import BaseStep
|
|
from ...components import R
|
|
|
|
|
|
@R.register("append_step")
|
|
class AppendStep(BaseStep):
|
|
"""Append `content` to the target file. If the file does not exist it is
|
|
created (a system notice is appended to the answer in that case).
|
|
|
|
Content is appended verbatim — callers control whether a separator newline
|
|
is included in the input."""
|
|
|
|
def _fail(self, message: str, **meta) -> None:
|
|
assert self.context is not None
|
|
self.context.response.success = False
|
|
self.context.response.answer = f"Error: {message}"
|
|
if meta:
|
|
self.context.response.metadata.update(meta)
|
|
|
|
async def execute(self): # pylint: disable=too-many-return-statements
|
|
assert self.context is not None
|
|
raw = str(self.context.get("path") or "")
|
|
content = self.context.get("content")
|
|
content_str = "" if content is None else str(content)
|
|
|
|
target, err = resolve_path(self.vault_path, raw)
|
|
if err:
|
|
self._fail(err)
|
|
return None
|
|
|
|
target, is_md = gate_md(target)
|
|
|
|
if target.exists() and not target.is_file():
|
|
self._fail(f"path {target} is not a file", path=str(target))
|
|
return None
|
|
|
|
created = not target.exists()
|
|
# Preserve the existing file's encoding so appended bytes don't corrupt
|
|
# a non-UTF-8 file (e.g. GBK CSV). New files default to UTF-8.
|
|
encoding = "utf-8" if created else await detect_file_encoding(target)
|
|
try:
|
|
if created:
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
try:
|
|
payload = content_str.encode(encoding)
|
|
except (UnicodeEncodeError, LookupError):
|
|
self.logger.warning(
|
|
f"[{self.name}] cannot encode appended content as {encoding!r}, falling back to utf-8",
|
|
)
|
|
payload = content_str.encode("utf-8")
|
|
async with aiofiles.open(str(target), "ab") as f:
|
|
await f.write(payload)
|
|
except Exception as e: # pylint: disable=broad-except
|
|
self._fail(f"write failed: {e}", path=str(target))
|
|
return None
|
|
|
|
nbytes = len(payload)
|
|
self.context.response.success = True
|
|
if created:
|
|
answer = f"Appended {nbytes} bytes to {target} [system notice: file did not exist and was auto-created]"
|
|
else:
|
|
answer = f"Appended {nbytes} bytes to {target}"
|
|
if not is_md:
|
|
answer = f"{answer} [system notice: {NON_MD_WARNING}]"
|
|
self.context.response.answer = answer
|
|
self.logger.info(
|
|
f"[{self.name}] appended path={target} bytes={nbytes} encoding={encoding} "
|
|
f"created={created} is_md={is_md}",
|
|
)
|
|
return self.context.response
|