Working on backing files.

Also started a sketches project to illustrate quick ideas.
This commit is contained in:
Kyle Isom 2023-10-11 23:27:42 -07:00
parent fd6e0c6899
commit 2dcc577f57
13 changed files with 590 additions and 30 deletions

View File

@ -1,7 +1,11 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<Objective-C>
<option name="INDENT_NAMESPACE_MEMBERS" value="0" />
<option name="INDENT_C_STRUCT_MEMBERS" value="8" />
<option name="INDENT_CLASS_MEMBERS" value="8" />
<option name="FUNCTION_BRACE_PLACEMENT" value="2" />
<option name="FUNCTION_TOP_AFTER_RETURN_TYPE_WRAP" value="2" />
</Objective-C>
<files>
<extensions>
@ -17,7 +21,16 @@
</extensions>
</files>
<codeStyleSettings language="ObjectiveC">
<option name="BLANK_LINES_BEFORE_IMPORTS" value="2" />
<option name="BLANK_LINES_AFTER_IMPORTS" value="2" />
<option name="BLANK_LINES_AROUND_METHOD" value="0" />
<option name="BLANK_LINES_AROUND_METHOD_IN_INTERFACE" value="0" />
<option name="INDENT_CASE_FROM_SWITCH" value="false" />
<option name="ALIGN_GROUP_FIELD_DECLARATIONS" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="INDENT_SIZE" value="8" />
<option name="CONTINUATION_INDENT_SIZE" value="4" />

View File

@ -0,0 +1,29 @@
#if ($HEADER_COMMENTS)
///
/// \file $FILE_NAME
/// \author $USER_NAME
/// \created $DATE
/// \brief ${NAME} ...
///
#if ($ORGANIZATION_NAME && $ORGANIZATION_NAME != "")
/// Copyright (c) $YEAR ${ORGANIZATION_NAME}#if (!$ORGANIZATION_NAME.endsWith(".")).#end All rights reserved.
#else
/// \section COPYRIGHT
/// Copyright $YEAR K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
#end
///
#end

View File

@ -0,0 +1,13 @@
#parse("C File Header.h")
#[[#ifndef]]# ${INCLUDE_GUARD}
#[[#define]]# ${INCLUDE_GUARD}
${NAMESPACES_OPEN}
class ${NAME} {
};
${NAMESPACES_CLOSE}
#[[#endif]]# // ${INCLUDE_GUARD}

124
Buffer.cc
View File

@ -20,12 +20,12 @@
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
/// @\section DESCRIPTION
#include <optional>
#include <random>
#include <sstream>
#include <iterator>
#include "Buffer.h"
@ -34,9 +34,9 @@ static std::string
anonymousName()
{
std::uniform_int_distribution<> dist(1000, 9999);
std::random_device randomDevice;
std::mt19937 rng(randomDevice());
std::stringstream ss;
std::random_device randomDevice;
std::mt19937 rng(randomDevice());
std::stringstream ss;
ss << "Buffer<" << dist(rng) << ">";
return ss.str();
@ -44,35 +44,137 @@ anonymousName()
Buffer::Buffer()
: name(anonymousName())
: dirty(false), name(anonymousName())
{
}
Buffer::Buffer(std::string fName)
: name(std::move(fName))
: dirty(false), name(std::move(fName))
{
}
Buffer::Buffer(std::string fName, std::string fPath)
: name(std::move(fName)), path(std::move(fPath))
: dirty(false), name(std::move(fName)), path(fPath)
{
if (this->path) {
this->file = OptFile(File(this->path.value()));
this->file = OptFile (File(fPath));
}
}
int Buffer::Flush(OptString altPath)
Buffer::FileStatus
Buffer::Flush(OptString altPath)
{
return altPath ? 0 : 1;
OptOutFileStream handle = std::nullopt;
if (altPath) {
auto altFile = File(altPath.value());
handle = altFile.Flush();
} else if (this->file) {
handle = this->file.value().Flush();
} else {
// At this point, we have no alternate path to write out
// and this Buffer isn't file-backed. From this point
// forward, we can operate under the assumption that
// `this->file` is a valid file.
return Buffer::FileStatus::FileStatusVirtual;
}
auto realFile = this->file.value();
if (!handle) {
if (realFile.IsReadOnly()) {
return Buffer::FileStatus::FileStatusReadOnly;
}
if (!realFile.IsWriteable()) {
return Buffer::FileStatus::FileStatusInvalidPermissions;
}
return Buffer::FileStatus::FileStatusIOFailed;
}
/// At this point, we know we're working with a valid file handle.
auto realHandle = handle.value();
if (!realHandle->good()) {
return Buffer::FileStatus::FileStatusIOFailed;
}
for (auto line : this->contents) {
std::copy(line.begin(), line.end(),
std::ostream_iterator<uint8_t>(*realHandle, ""));
}
realHandle->flush();
realHandle->close();
delete realHandle;
this->dirty = false;
return Buffer::FileStatus::FileStatusOK;
}
void Buffer::ChangePath(std::string newPath)
Buffer::FileStatus
Buffer::Refresh()
{
if (this->IsVirtual()) {
return Buffer::FileStatus::FileStatusVirtual;
}
auto realFile = this->file.value();
auto handle = realFile.Refresh();
if (!handle) {
return FileStatus::FileStatusNonExistent;
}
auto realHandle = handle.value();
this->clearContents();
this->dirty = false; // clean slate, kiddo
/// \todo Moral quandary: if the file disappears from disk, we
/// probably want to know that, but maybe clearing the
/// contents first isn't the right move.
if (!realHandle->good()) {
delete realHandle;
return FileStatus::FileStatusIOFailed;
}
size_t currentLine = 0;
while (realHandle->good()) {
}
}
void
Buffer::ChangePath(std::string newPath)
{
this->path = OptString(std::move(newPath));
}
void
Buffer::clearContents()
{
for (auto & line : this->contents) {
line.clear();
}
this->contents.clear();
}
void
Buffer::MarkDirty()
{
if (this->dirty) {
return;
}
this->dirty = true;
}

View File

@ -17,7 +17,7 @@
#include "Cursor.h"
typedef std::vector<std::vector<uint8_t>> BufferContents;
typedef std::vector<std::vector<uint8_t>> BufferContents;
/// A Buffer is the atom of text editing. It represents a single document,
@ -32,6 +32,38 @@ typedef std::vector<std::vector<uint8_t>> BufferContents;
/// cannot be demoted to a virtual buffer.
class Buffer {
public:
enum class FileStatus : uint8_t {
/// The file operation succeeded correctly.
FileStatusOK = 0,
/// The file can't be written to because it is marked
/// read-only. This refers to a buffer being marked as
/// read-only, not to whether the underlying file is
/// actually read-only.
FileStatusReadOnly = 1,
/// There was an I/O error trying to write to the file.
FileStatusIOFailed = 2,
/// The Buffer couldn't be flushed because it is a virtual
/// buffer. If the user explicitly tried to save the buffer,
/// they should be prompted for a path.
FileStatusVirtual = 3,
/// The underlying file doesn't have the right permissions;
/// for example, it's not writeable if the user is trying to
/// write the file.
FileStatusInvalidPermissions = 4,
/// The underlying file doesn't exist.
FileStatusNonExistent = 5,
};
static bool StatusOK(FileStatus status)
{ return status == FileStatus::FileStatusOK; }
/// The constructor with no arguments generates a new anonymous
/// buffer.
Buffer();
@ -42,18 +74,42 @@ public:
/// Instantiate a Buffer pointing to fPath.
Buffer(std::string fName, std::string fPath);
std::string Name() const { return this->name; }
std::string Name() const
{ return this->name; }
int Flush(OptString altPath);
void ChangePath(std::string newPath);
bool IsVirtual()
{ return this->file.has_value(); }
Buffer::FileStatus Flush(OptString altPath);
/// Refresh reads the contents of the file back into the
/// buffer.
///
/// \warning This does not care if the file is dirty or not -
/// it WILL overwrite the contents of the buffer.
///
/// \return A FileStatus indicating whether the read was successful.
Buffer::FileStatus Refresh();
void ChangePath(std::string newPath);
Cursor Cursor()
{ return this->cursor; }
void MarkDirty();
bool IsDirty()
{ return this->dirty; }
private:
std::string name;
OptString path;
OptFile file;
Cursor cursor;
BufferContents contents;
void clearContents();
class Cursor cursor;
bool dirty;
std::string name;
OptString path;
OptFile file;
BufferContents contents;
};

12
Defs.h
View File

@ -28,8 +28,13 @@
#include <string>
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
typedef std::optional<std::string> OptString;
template<typename T>
T Min(T a, T b) { return a > b ? b : a; }
template size_t Min<size_t>(size_t a, size_t b);
@ -39,4 +44,11 @@ T Max(T a, T b) { return a > b ? a : b; }
template size_t Max(size_t a, size_t b);
/// \todo Consider abstracting platforms to a separate subsystem.
static const std::string PlatformLinux("Linux");
static const std::string PlatformApple("Darwin");
static const std::string PlatformWindows("Windows");
static const std::string PlatformCurrent(TOSTRING(KGE_PLATFORM));
#endif // KEPP__DEFS_H_

85
File.cc
View File

@ -21,12 +21,13 @@
/// SOFTWARE.
///
#include <filesystem>
#include "File.h"
const std::string &File::Path() const
const std::string File::Path()
{
return path;
return this->path.string();
}
@ -41,3 +42,83 @@ File::File(std::string fPath)
{
}
OptOutFileStream File::Flush()
{
// Exit early if we shouldn't be writing to the file.
if (this->IsReadOnly()) {
return std::nullopt;
}
if (!this->Exists()) {
if (!this->IsWriteable()) {
return std::nullopt;
}
return {new std::ofstream(this->path, this->mode|std::ios::trunc)};
}
return {new std::ofstream(this->path, this->mode)};
}
OptInFileStream
File::Refresh()
{
if (!this->Exists()) {
return std::nullopt;
}
return {new std::ifstream(this->path, this->mode)};
}
size_t
File::Size()
{
return std::filesystem::file_size(this->path);
}
bool
File::Exists()
{
return std::filesystem::exists(this->path);
}
static bool
checkAccess(std::filesystem::path path, std::filesystem::perms mode, bool checkParent)
{
if (!std::filesystem::exists(path) && checkParent) {
auto fullPath = std::filesystem::absolute(path);
auto parent = fullPath.parent_path();
auto dirEnt = std::filesystem::directory_entry(parent);
auto perms = dirEnt.status().permissions();
return (perms & mode) != std::filesystem::perms::none;
}
return (status(path).permissions() & mode) != std::filesystem::perms::none;
}
bool
File::IsWriteable()
{
return checkAccess(this->path, std::filesystem::perms::owner_write, true);
}
bool
File::IsReadable()
{
return checkAccess(this->path, std::filesystem::perms::owner_read, false);
}
bool
File::IsReadWriteable()
{
auto checkMode = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write;
return checkAccess(this->path, checkMode, true);
}

36
File.h
View File

@ -25,6 +25,7 @@
#define KEPP__FILE_H_
#include <filesystem>
#include <fstream>
#include <ios>
#include <string>
@ -32,6 +33,12 @@
#include "Defs.h"
typedef std::optional<std::ofstream *> OptOutFileStream;
typedef std::optional<std::ifstream *> OptInFileStream;
/// Files default to being read/write.
static constexpr std::ios::openmode DefaultMode =
std::ios::in|std::ios::out;
@ -41,18 +48,41 @@ class File {
public:
File(std::string fPath);
const std::string &Path() const;
const std::string Path();
void SetPath(const std::string &fPath);
// int Refresh(std::);
OptOutFileStream Flush();
OptInFileStream Refresh();
/// The readonly attribute on a File is a virtual write
/// protection, and does not reflect the permissions on
/// the file itself. For that, see IsWriteable.
[[nodiscard]] bool IsReadOnly() const { return this->readOnly; };
void MarkReadOnly() { this->readOnly = true; }
void ClearReadOnly() { this->readOnly = false; }
/// Size returns the size of the file on disk.
size_t Size();
/// Exists checks whether the file exists on disk.
bool Exists();
/// IsWriteable checks whether the current user has write
/// permissions for the file.
bool IsWriteable();
/// IsReadable checks whether the current user has read
/// permissions on the file.
bool IsReadable();
/// IsReadWriteable checks whether the current user has both
/// read and write permissions on the file.
bool IsReadWriteable();
private:
std::string path;
std::filesystem::path path;
bool readOnly;
std::ios::openmode mode;
};

10
main.cc
View File

@ -26,12 +26,14 @@
#include "Buffer.h"
#include "Cursor.h"
#include "LineEnding.h"
static void
usage(std::ostream &os, int exitCode)
{
os << "ke - kyle's editor ++\n";
os << "ke - kyle's editor version " << TOSTRING(KGE_VERSION)
<< "/" << PlatformCurrent << "\n";
os << "\nUsage:\n";
os << "\tke [files]\n";
exit(exitCode);
@ -46,18 +48,16 @@ ShowDist(Cursor a, Cursor b)
int
main(int argc, char **argv)
main(int argc, char *argv[])
{
if ((argc == 2) && (std::string(argv[1]) == "-h")) {
std::cout << "help?\n";
usage(std::cout, 0);
}
Buffer frame;
std::cout << frame.Name() << "\n";
ShowDist(Cursor(0, 0), Cursor(5, 5));
ShowDist(Cursor(2, 2), Cursor(4, 4));
ShowDist(Cursor(32, 12), Cursor(14, 71));
return 0;
}

35
sketches/CMakeLists.txt Normal file
View File

@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.25)
project(ke_sketches
DESCRIPTION "sketches and small test programs for kyle's editor"
LANGUAGES CXX
VERSION 0.0.1)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_VERBOSE_MAKEFILES TRUE)
set(VERBOSE YES)
if (MSVC)
add_compile_options("/W4" "$<$<CONFIG:RELEASE>:/O2>")
else ()
add_compile_options(
"-Wall"
"-Wextra"
"-Werror"
"-static"
"$<$<CONFIG:DEBUG>:-g>"
"$<$<CONFIG:RELEASE>:-O2>")
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
add_compile_options(
"-stdlib=libc++"
"-fsanitize=address"
"-fno-omit-frame-pointer"
"-fsanitize-address-use-after-scope"
)
else ()
# nothing special for gcc at the moment
endif ()
endif ()
add_executable(readFile ReadFile.cc)
add_executable(enoent enoent.cc)

87
sketches/ReadFile.cc Normal file
View File

@ -0,0 +1,87 @@
///
/// \file ReadFile.cc
/// \author kyle
/// \created 2023-10-11
/// \brief ReadFile tests reading a file.
///
/// \section COPYRIGHT
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
#include <cstdint>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
int
readFile(const std::string path)
{
std::cout << "[+] opening " << path << "\n";
std::ifstream input(path, std::ios::in);
if (!input.good()) {
std::cerr << "[!] failed to open " << path << "\n";
input.close();
return -1;
}
std::cout << "[+] reading from " << path << "\n";
std::vector<std::vector<uint8_t>> doc;
while (input.good()) {
std::string temp;
while (std::getline(input, temp)) {
std::cout << "[+] line: " << temp << "\n";
std::vector<uint8_t> row(temp.begin(), temp.end());
doc.push_back(row);
}
}
std::cout << "[+] finished reading document\n";
std::cout << "\tgood: " << input.good() << "\n";
std::cout << "\tfail: " << input.fail() << "\n";
std::cout << "\t bad: " << input.bad() << "\n";
std::cout << "\t eof: " << input.eof() << "\n";
input.close();
std::cout << "[+] dumping document of " << doc.size() << " lines.\n";
for (size_t i = 0; i < doc.size(); i++) {
std::cout << "line " << std::setw(3) << i << ": ";
std::copy(doc[i].begin(), doc[i].end(),
std::ostream_iterator<uint8_t>(std::cout, ""));
std::cout << "\n";
}
return 0;
}
int
main(int argc, char *argv[])
{
for (int i = 1; i < argc; i++) {
std::cout << "[+] input file: " << argv[i] << "\n";
if (readFile(std::string(argv[i])) != 0) {
std::cerr << "[!] failed\n";
} else {
std::cout << "[+] OK\n";
}
}
}

93
sketches/enoent.cc Normal file
View File

@ -0,0 +1,93 @@
///
/// \file enoent.cc
/// \author kyle
/// \created 2023-10-11
/// \brief basic file system checks
///
/// \section COPYRIGHT
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
#include <algorithm>
#include <iostream>
#include <iterator>
#include <fstream>
#include <filesystem>
#include <vector>
using namespace std;
int
main()
{
namespace fs = std::filesystem;
std::string input = "ohai\n";
std::vector<uint8_t> buffer(input.begin(), input.end());
auto f = fs::path("ENOENT2");
if (fs::exists(f)) {
std::cout << "[+] the file exists\n";
std::cout << "\tfile size: " << fs::file_size(f) << "\n";
} else {
std::cout << "the file does not exist\n";
}
cout << "[+] attempting to open the file.\n";
ofstream handle(f, std::ios::in | std::ios::out | std::ios::trunc);
if (!handle.good()) {
cerr << "[!] file operation failed\n";
cerr << "\tfile fail: " << handle.fail() << "\n";
cerr << "\t file eof: " << handle.eof() << "\n";
cerr << "\t file bad: " << handle.bad() << "\n";
return 1;
}
if (fs::exists(f)) {
std::cout << "[+] the file exists\n";
std::cout << "\tfile size: " << fs::file_size(f) << "\n";
} else {
std::cout << "the file does not exist\n";
}
handle.flush();
if (fs::exists(f)) {
std::cout << "[+] the file exists\n";
std::cout << "\tfile size: " << fs::file_size(f) << "\n";
} else {
std::cout << "the file does not exist\n";
}
cout << "[+] file open successfully. attempting to write.\n";
handle << "Hello, world.\r\n";
if (fs::exists(f)) {
std::cout << "\tfile size: " << fs::file_size(f) << "\n";
}
cout << "[+] attempting to write the vector to file.\n";
std::copy(buffer.begin(), buffer.end(), std::ostream_iterator<uint8_t>(handle, ""));
cout << "[+] closing the file.\n";
handle.close();
if (fs::exists(f)) {
std::cout << "\tfile size: " << fs::file_size(f) << "\n";
}
return 0;
}

9
sketches/testShort.txt Normal file
View File

@ -0,0 +1,9 @@
this is a short text file.
just wanted to see what it looks like.
of course
of course. This is more of a paragraph that should end |
at the pipes, which should be a border. It's one way to|
test it's being read correctly.