diff --git a/Bonsai.System/IO/ReadAllText.cs b/Bonsai.System/IO/ReadAllText.cs new file mode 100644 index 00000000..1479c49f --- /dev/null +++ b/Bonsai.System/IO/ReadAllText.cs @@ -0,0 +1,36 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Reactive.Linq; + +namespace Bonsai.IO +{ + /// + /// Represents an operator that opens a text file, returns a single string with all + /// lines in the file, and then closes the file. + /// + [DefaultProperty(nameof(Path))] + [Description("Opens a text file, returns a single string with all lines in the file, and then closes the file.")] + public class ReadAllText : Source + { + /// + /// Gets or sets the relative or absolute path of the file to open for reading. + /// + [Description("The relative or absolute path of the file to open for reading.")] + [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] + public string Path { get; set; } + + /// + /// Generates an observable sequence that opens the text file, returns a single string + /// with all lines in the file, and then closes the file. + /// + /// + /// A sequence containing a single string with all lines in the file. + /// + public override IObservable Generate() + { + var path = Path; + return Observable.Defer(() => Observable.Return(File.ReadAllText(path))); + } + } +} diff --git a/Bonsai.System/IO/WriteAllText.cs b/Bonsai.System/IO/WriteAllText.cs new file mode 100644 index 00000000..c61ffd9a --- /dev/null +++ b/Bonsai.System/IO/WriteAllText.cs @@ -0,0 +1,64 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Reactive.Linq; + +namespace Bonsai.IO +{ + /// + /// Represents an operator that opens a text file, writes the source string to + /// the file, and then closes the file. + /// + [DefaultProperty(nameof(Path))] + [Description("Creates a new file, writes the source string to the file, and then closes the file.")] + public class WriteAllText : Sink + { + /// + /// Gets or sets the relative or absolute path of the file to open for writing. + /// + [Description("The relative or absolute path of the file to open for writing.")] + [Editor("Bonsai.Design.SaveFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] + public string Path { get; set; } + + /// + /// Gets or sets a value indicating whether the output file should be overwritten if it already exists. + /// + [Description("Indicates whether the output file should be overwritten if it already exists.")] + public bool Overwrite { get; set; } + + /// + /// Gets or sets a value indicating whether text should be appended to the output file if it already exists. + /// + [Description("Indicates whether text should be appended to the output file if it already exists.")] + public bool Append { get; set; } + + /// + /// Creates a new file, writes the string in the observable sequence to the file, + /// and then closes the file. + /// + /// + /// The sequence containing the string to write to the file. + /// + /// + /// An observable sequence that is identical to the sequence + /// but where there is an additional side effect of writing the string to the file. + /// + public override IObservable Process(IObservable source) + { + var path = Path; + var overwrite = Overwrite; + var append = Append; + return source.Do(contents => + { + PathHelper.EnsureDirectory(path); + if (File.Exists(path) && !overwrite && !append) + { + throw new IOException($"The file '{path}' already exists."); + } + + using var writer = new StreamWriter(path, append); + writer.Write(contents); + }); + } + } +}