From c87f9627efc8b612eb9b000acfcc6731cad15765 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Tue, 7 Oct 2025 09:12:22 +0200 Subject: Initial commit --- .clang-format | 315 +++++++++++++++++++++++++++++++++++++ .clang-tidy | 2 + .dir-locals.el | 12 ++ .gitignore | 2 + meson.build | 185 ++++++++++++++++++++++ src/args.cc | 389 ++++++++++++++++++++++++++++++++++++++++++++++ src/args.hh | 64 ++++++++ src/buffer.cc | 213 +++++++++++++++++++++++++ src/buffer.hh | 31 ++++ src/check.hh | 39 +++++ src/config.h.in | 1 + src/io.cc | 232 ++++++++++++++++++++++++++++ src/io.hh | 51 ++++++ src/line.cc | 127 +++++++++++++++ src/line.hh | 37 +++++ src/main.cc | 31 ++++ src/str.cc | 53 +++++++ src/str.hh | 21 +++ src/unique_fd.cc | 9 ++ src/unique_fd.hh | 36 +++++ test/args.cc | 412 +++++++++++++++++++++++++++++++++++++++++++++++++ test/buffer.cc | 270 ++++++++++++++++++++++++++++++++ test/io.cc | 203 ++++++++++++++++++++++++ test/io_test_helper.cc | 82 ++++++++++ test/io_test_helper.hh | 18 +++ test/line.cc | 182 ++++++++++++++++++++++ test/str.cc | 67 ++++++++ 27 files changed, 3084 insertions(+) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .dir-locals.el create mode 100644 .gitignore create mode 100644 meson.build create mode 100644 src/args.cc create mode 100644 src/args.hh create mode 100644 src/buffer.cc create mode 100644 src/buffer.hh create mode 100644 src/check.hh create mode 100644 src/config.h.in create mode 100644 src/io.cc create mode 100644 src/io.hh create mode 100644 src/line.cc create mode 100644 src/line.hh create mode 100644 src/main.cc create mode 100644 src/str.cc create mode 100644 src/str.hh create mode 100644 src/unique_fd.cc create mode 100644 src/unique_fd.hh create mode 100644 test/args.cc create mode 100644 test/buffer.cc create mode 100644 test/io.cc create mode 100644 test/io_test_helper.cc create mode 100644 test/io_test_helper.hh create mode 100644 test/line.cc create mode 100644 test/str.cc diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..aa4a4dc --- /dev/null +++ b/.clang-format @@ -0,0 +1,315 @@ +--- +Language: Cpp +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: true + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseArrows: false + AlignCaseColons: false +AlignConsecutiveTableGenBreakingDAGArgColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveTableGenCondOperatorColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveTableGenDefinitionColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never +AllowShortCaseExpressionOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AllowShortNamespacesOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: BinPack +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BracedInitializerIndentWidth: 2 +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakAfterReturnType: None +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakBinaryOperations: Never +BreakConstructorInitializers: BeforeColon +BreakFunctionDefinitionParameters: false +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +BreakTemplateDeclarations: Yes +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: true +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '([-_](lzma|z|test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: true +IndentExportBlock: true +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: AfterHash +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLines: + AtEndOfFile: false + AtStartOfBlock: false + AtStartOfFile: true +KeepFormFeed: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MainIncludeChar: Quote +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: NextLine +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakBeforeMemberAccess: 150 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +PPIndentWidth: 1 +QualifierAlignment: Leave +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + - ParseTestProto + - ParsePartialTestProto + CanonicalDelimiter: pb + BasedOnStyle: google +ReferenceAlignment: Pointer +ReflowComments: IndentOnly +RemoveBracesLLVM: false +RemoveEmptyLinesInUnwrappedLines: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + ExceptDoubleParentheses: false + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TableGenBreakInsideDAGArg: DontBreak +TabWidth: 8 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +WrapNamespaceBodyWithEmptyLines: Leave +... + diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..bd4deb9 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,2 @@ +--- +Checks: 'bugprone-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-bugprone-easily-swappable-parameters,-bugprone-unchecked-optional-access,-misc-non-private-member-variables-in-classes,-misc-const-correctness,-misc-no-recursion,-modernize-avoid-c-arrays,-modernize-use-trailing-return-type,-readability-magic-numbers,-readability-identifier-length,-readability-braces-around-statements,-readability-function-cognitive-complexity,-readability-redundant-inline-specifier,-readability-implicit-bool-conversion,-readability-qualified-auto' diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..224840b --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,12 @@ +;;; Directory Local Variables -*- no-byte-compile: t; -*- +;;; For more information see (info "(emacs) Directory Variables") + +((c++-mode . ((eval + . + (let ((project-path + (locate-dominating-file default-directory ".dir-locals.el"))) + (setq-local flycheck-clangcheck-build-path + (concat project-path "build")) + (setq-local flycheck-clang-language-standard "c++23") + (setq-local flycheck-clang-definitions '("HAVE_CONFIG_H")) + (setq-local flycheck-clang-include-path '("../src" "../build"))))))) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db9f3cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build/ +/compile_commands.json diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..f98276c --- /dev/null +++ b/meson.build @@ -0,0 +1,185 @@ +project( + 'jkc', + 'cpp', + version : '0.1', + meson_version : '>= 1.3.0', + default_options : [ + 'warning_level=3', + 'cpp_std=c++26', + 'cpp_eh=none', + 'cpp_rtti=false', + 'default_library=static', + ], +) + +cpp_flags = [] +cpp_optional_flags = [] +if get_option('buildtype') == 'release' + # If asserts are disabled parameters and variables used for only that + # end up causing warnings + cpp_optional_flags += ['-Wno-unused-parameter', '-Wno-unused-variable', + '-Wno-unused-but-set-variable'] + cpp_flags += '-DNDEBUG' +endif +cpp = meson.get_compiler('cpp') +foreach flag : cpp_optional_flags + if cpp.has_argument(flag) + cpp_flags += flag + endif +endforeach +add_project_arguments([cpp_flags], language: 'cpp') + +conf_data = configuration_data() +conf_data.set('version', meson.project_version()) +configure_file(input: 'src/config.h.in', + output: 'config.h', + configuration : conf_data) + +dbus_dep = dependency('sdbus-c++', version: '>= 2.0.0') + +inc = include_directories('src') + +args_lib = library( + 'args', + sources: [ + 'src/args.cc', + 'src/args.hh', + ], + include_directories: inc, +) +args_dep = declare_dependency(link_with: args_lib) + +buffer_lib = library( + 'buffer', + sources: [ + 'src/buffer.cc', + 'src/buffer.hh', + ], + include_directories: inc, +) +buffer_dep = declare_dependency(link_with: buffer_lib) + +io_lib = library( + 'io', + sources: [ + 'src/line.cc', + 'src/line.hh', + 'src/io.cc', + 'src/io.hh', + 'src/unique_fd.cc', + 'src/unique_fd.hh', + ], + include_directories: inc, +) +io_dep = declare_dependency(link_with: io_lib) + +str_lib = library( + 'str', + sources: [ + 'src/str.cc', + 'src/str.hh', + ], + include_directories: inc, +) +str_dep = declare_dependency(link_with: str_lib) + +bluetooth_jukebox = executable( + 'bluetooth-jukebox', + sources: [ + 'src/main.cc', + ], + include_directories: inc, + install : true, + dependencies : [ + args_dep, + io_dep, + str_dep, + ], +) + +gtest_main_dep = dependency('gtest_main', fallback : ['gtest_main']) + +test_dependencies = [ + gtest_main_dep, +] + +test('args', executable( + 'test_args', + sources: ['test/args.cc'], + include_directories: inc, + dependencies: [ + args_dep, + test_dependencies, + ], +)) + +io_test_helper_lib = library( + 'io_test_helper', + sources: [ + 'test/io_test_helper.cc', + 'test/io_test_helper.hh', + ], + include_directories: inc, + dependencies: io_dep, +) +io_test_helper_dep = declare_dependency( + link_with: io_test_helper_lib, + dependencies: io_dep, +) + +test('line', executable( + 'test_line', + sources: ['test/line.cc'], + include_directories: inc, + dependencies: [ + io_dep, + io_test_helper_dep, + test_dependencies, + ], +)) + +test('str', executable( + 'test_str', + sources: ['test/str.cc'], + include_directories: inc, + dependencies: [ + str_dep, + test_dependencies, + ], +)) + +test('io', executable( + 'test_io', + sources: ['test/io.cc'], + include_directories: inc, + dependencies: [ + io_dep, + io_test_helper_dep, + test_dependencies, + ], +)) + +test('buffer', executable( + 'test_buffer', + sources: ['test/buffer.cc'], + include_directories: inc, + dependencies : [ + buffer_dep, + test_dependencies, + ], +)) + +run_clang_tidy = find_program('run-clang-tidy', required: false) + +if run_clang_tidy.found() + # The clang-tidy target generated by meson misses most of the + # source files, so create our own. + run_target( + 'clang-tidy', + command: [ + run_clang_tidy, + '-quiet', + '-use-color', + ], + ) +endif diff --git a/src/args.cc b/src/args.cc new file mode 100644 index 0000000..1794941 --- /dev/null +++ b/src/args.cc @@ -0,0 +1,389 @@ +#include "args.hh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::string kEmpty; + +class OptionImpl : public Args::OptionArgument { + public: + enum Type : uint8_t { + NoArgument, + RequiredArgument, + OptionalArgument, + }; + + OptionImpl(Type type, char shortname, std::string longname, std::string arg, + std::string help) + : type(type), + shortname(shortname), + longname(std::move(longname)), + arg(std::move(arg)), + help(std::move(help)) {} + + const Type type; + const char shortname; + const std::string longname; + const std::string arg; + const std::string help; + + [[nodiscard]] bool is_set() const override { return is_set_; } + + [[nodiscard]] bool has_argument() const override { + return value_.has_value(); + } + + [[nodiscard]] const std::string& argument() const override { + if (value_.has_value()) + return value_.value(); + assert(false); + return kEmpty; + } + + void clear() { + is_set_ = false; + value_.reset(); + } + + void set_argument(std::string value) { + assert(type == Type::RequiredArgument || type == Type::OptionalArgument); + is_set_ = true; + value_ = std::move(value); + } + + void set_no_argument() { + assert(type == Type::NoArgument || type == Type::OptionalArgument); + is_set_ = true; + value_.reset(); + } + + private: + bool is_set_{false}; + std::optional value_; +}; + +class ArgsImpl : public Args { + public: + explicit ArgsImpl(std::string prgname) : prgname_(std::move(prgname)) {} + + std::shared_ptr