From c95c0ca4c10abb0a8a95f2e549be64a71746b058 Mon Sep 17 00:00:00 2001 From: kilo52 Date: Thu, 29 Jan 2026 16:03:26 +0100 Subject: [PATCH 1/2] Improved displayed label for logical line count. Changes how count results for logical lines are shown by scount. For individual files, summary rows and the total row, as well as for single file inputs, if the corresponding source file entity has a format for which logical lines cannot be computed, the shown result number is displayed as a 'n/a' label. Adds the bool 'hasLogicalLines' field to the RcnCountResultGroup type to expose the needed information to scount. Signed-off-by: kilo52 --- src/lib/c/statistics.c | 3 +- src/lib/include/reckon/reckon.h | 13 ++++++ src/lib/tests/unit/c/test_statistics.c | 26 +++++++++++ src/scount/c/print.c | 45 +++++++++++++++++-- .../functionality/res/expected/mixed.txt | 12 ++--- .../res/expected/mixedWithSyntaxError.txt | 4 +- .../expected/output_multiple_files_no_llc.txt | 21 +++++++++ .../expected/output_single_file_no_llc.txt | 8 ++++ .../tests/functionality/sh/test_scount.sh | 14 ++++++ src/scount/tests/unit/c/test_print.c | 2 + 10 files changed, 135 insertions(+), 13 deletions(-) create mode 100644 src/scount/tests/functionality/res/expected/output_multiple_files_no_llc.txt create mode 100644 src/scount/tests/functionality/res/expected/output_single_file_no_llc.txt diff --git a/src/lib/c/statistics.c b/src/lib/c/statistics.c index ba5e8df..347aa44 100644 --- a/src/lib/c/statistics.c +++ b/src/lib/c/statistics.c @@ -250,10 +250,11 @@ static inline bool count( RCN_LOG_DBG(file->path) bool ok = false; + result->hasLogicalLines = detected.isProgrammingLanguage; RcnTextFormat sourceFormat = detected.format; ok = ensureFileContent(stats, options, file, result); if (ok && options.operations & RCN_OPT_COUNT_LOGICAL_LINES){ - if (detected.isProgrammingLanguage) { + if (result->hasLogicalLines) { ok = countLogicalLines(stats, file, sourceFormat, result); } } diff --git a/src/lib/include/reckon/reckon.h b/src/lib/include/reckon/reckon.h index 856768e..b8ba7c8 100644 --- a/src/lib/include/reckon/reckon.h +++ b/src/lib/include/reckon/reckon.h @@ -347,6 +347,19 @@ typedef struct RcnCountResultGroup { */ bool isProcessed; + /** + * Indicates whether logical lines can be computed for the source entity. + * + * If `true`, the `logicalLines` field contains a valid count. If `false`, + * then logical lines are not applicable for the source entity's format, + * e.g. for plain text files, and the `logicalLines` field is zero. + * This field is only set by a counting operation and remains initialized + * as `false` if no such operation was performed. + * + * @since 1.1.0 + */ + bool hasLogicalLines; + } RcnCountResultGroup; /** diff --git a/src/lib/tests/unit/c/test_statistics.c b/src/lib/tests/unit/c/test_statistics.c index 4786746..425ba3d 100644 --- a/src/lib/tests/unit/c/test_statistics.c +++ b/src/lib/tests/unit/c/test_statistics.c @@ -219,6 +219,31 @@ void testCountWithMultipleFilesWhenOneFileHasError(void) { rcnFreeCountStatistics(stats); } +void testCountResultGroupLogicalLineCheckField(void) { + char* path = RECKON_TEST_PATH_RES_BASE "/mixed"; + RcnCountStatistics* stats = rcnCreateCountStatistics(path); + RcnStatOptions options = {0}; + rcnCount(stats, options); + TEST_ASSERT_EQUAL_INT(4, stats->count.size); + RcnSourceFile* fileJava = &stats->count.files[0]; + RcnCountResultGroup* resultJava = &stats->count.results[0]; + TEST_ASSERT_EQUAL_STRING("Source.java", fileJava->name); + TEST_ASSERT_TRUE(resultJava->hasLogicalLines); + RcnSourceFile* fileC = &stats->count.files[1]; + RcnCountResultGroup* resultC = &stats->count.results[1]; + TEST_ASSERT_EQUAL_STRING("source.c", fileC->name); + TEST_ASSERT_TRUE(resultC->hasLogicalLines); + RcnSourceFile* fileTxt = &stats->count.files[2]; + RcnCountResultGroup* resultTxt = &stats->count.results[2]; + TEST_ASSERT_EQUAL_STRING("text.txt", fileTxt->name); + TEST_ASSERT_FALSE(resultTxt->hasLogicalLines); + RcnSourceFile* fileMd = &stats->count.files[3]; + RcnCountResultGroup* resultMd = &stats->count.results[3]; + TEST_ASSERT_EQUAL_STRING("text2.md", fileMd->name); + TEST_ASSERT_FALSE(resultMd->hasLogicalLines); + rcnFreeCountStatistics(stats); +} + // NOLINTEND(readability-magic-numbers) int main(void) { @@ -229,5 +254,6 @@ int main(void) { RUN_TEST(testCountWithFileWhenContentIsNullAndStatusIsFileError); RUN_TEST(testCountWhenFileHasUnsupportedFormat); RUN_TEST(testCountWithMultipleFilesWhenOneFileHasError); + RUN_TEST(testCountResultGroupLogicalLineCheckField); return UNITY_END(); } diff --git a/src/scount/c/print.c b/src/scount/c/print.c index 12703c4..8d08976 100644 --- a/src/scount/c/print.c +++ b/src/scount/c/print.c @@ -44,6 +44,7 @@ static const char TABLE_BORDER_VERTICAL_NORMAL = '|'; static const char TABLE_BORDER_VERTICAL_EMPHASIS = '|'; static const char TABLE_BORDER_CORNER = 'o'; static const char* TABLE_PADDING_LEFT = " "; +static const char* LABEL_NOT_APPLICABLE = "n/a"; static const char errorMessage[] = "Error"; #ifdef _WIN32 @@ -121,6 +122,15 @@ static bool ensureCapacity(PrintBuffer* buffer, size_t additional) { return true; } +static bool hasAnyLogicalLines(const RcnCountStatistics* stats) { + for (size_t i = 0; i < stats->count.size; ++i) { + if (stats->count.results[i].hasLogicalLines) { + return true; + } + } + return false; +} + /** * Puts a string up to `length` into the buffer. * The specified length must not exceed the actual length of `string`. @@ -233,6 +243,13 @@ static void prCnt(PrintBuffer* buffer, RcnCount value, int width) { prRpt(buffer, ' ', right); } +static void prNotApplicable(PrintBuffer* buffer) { + const size_t pad = ((WIDTH_COL1 - strlen(LABEL_NOT_APPLICABLE)) / 2) - 1; + prRpt(buffer, ' ', pad); + prStr(buffer, LABEL_NOT_APPLICABLE); + prRpt(buffer, ' ', pad); +} + static void prHeaderCell(PrintBuffer* buffer, const char* label, int width) { assert(label != NULL); const int length = (int) strlen(label); @@ -384,7 +401,11 @@ static void prFileRowData( prChr(buffer, ' '); prChr(buffer, TABLE_BORDER_VERTICAL_NORMAL); prChr(buffer, ' '); - prCnt(buffer, res->logicalLines, WIDTH_COL1); + if (res->hasLogicalLines) { + prCnt(buffer, res->logicalLines, WIDTH_COL1); + } else { + prNotApplicable(buffer); + } prChr(buffer, ' '); prChr(buffer, TABLE_BORDER_VERTICAL_NORMAL); prChr(buffer, ' '); @@ -443,6 +464,7 @@ static void prSummaryRows( ) { for (RcnTextFormat frmt = 0; frmt < RECKON_NUM_SUPPORTED_FORMATS; ++frmt) { const char* label = NULL; + bool hasLogicalLines = false; switch (frmt) { case RCN_TEXT_UNFORMATTED: label = "Plain Text"; @@ -452,9 +474,11 @@ static void prSummaryRows( break; case RCN_LANG_C: label = "C"; + hasLogicalLines = true; break; case RCN_LANG_JAVA: label = "Java"; + hasLogicalLines = true; break; // LCOV_EXCL_START default: @@ -475,7 +499,11 @@ static void prSummaryRows( prChr(buffer, ' '); prChr(buffer, TABLE_BORDER_VERTICAL_NORMAL); prChr(buffer, ' '); - prCnt(buffer, stats->logicalLines[frmt], WIDTH_COL1); + if (hasLogicalLines) { + prCnt(buffer, stats->logicalLines[frmt], WIDTH_COL1); + } else { + prNotApplicable(buffer); + } prChr(buffer, ' '); prChr(buffer, TABLE_BORDER_VERTICAL_NORMAL); prChr(buffer, ' '); @@ -506,7 +534,11 @@ static void prTotalsRow(PrintBuffer* buffer, const RcnCountStatistics* stats) { prChr(buffer, ' '); prChr(buffer, TABLE_BORDER_VERTICAL_NORMAL); prChr(buffer, ' '); - prCnt(buffer, stats->totalLogicalLines, WIDTH_COL1); + if (hasAnyLogicalLines(stats)) { + prCnt(buffer, stats->totalLogicalLines, WIDTH_COL1); + } else { + prNotApplicable(buffer); + } prChr(buffer, ' '); prChr(buffer, TABLE_BORDER_VERTICAL_NORMAL); prChr(buffer, ' '); @@ -540,7 +572,12 @@ PrintBuffer printResultSingle(const RcnCountStatistics* stats) { prChr(&buffer, '\n'); prChr(&buffer, '\n'); prStr(&buffer, " Logical Lines of Code (LLC): "); - pr8ld(&buffer, result->logicalLines); + if (result->hasLogicalLines) { + pr8ld(&buffer, result->logicalLines); + } else { + prRpt(&buffer, ' ', 8 - strlen(LABEL_NOT_APPLICABLE)); + prStr(&buffer, LABEL_NOT_APPLICABLE); + } prChr(&buffer, '\n'); prStr(&buffer, " Physical Lines (PHL): "); pr8ld(&buffer, result->physicalLines); diff --git a/src/scount/tests/functionality/res/expected/mixed.txt b/src/scount/tests/functionality/res/expected/mixed.txt index f094722..c418eec 100644 --- a/src/scount/tests/functionality/res/expected/mixed.txt +++ b/src/scount/tests/functionality/res/expected/mixed.txt @@ -5,18 +5,18 @@ Scanned files: 8 | Sample1.java | 3 | 12 | 34 | 233 | 233 | | Sample2.java | 4 | 13 | 38 | 286 | 286 | | sample1.c | 4 | 10 | 29 | 180 | 180 | - | sample1.md | 0 | 1 | 8 | 52 | 52 | - | sample1.txt | 0 | 1 | 9 | 52 | 52 | + | sample1.md | n/a | 1 | 8 | 52 | 52 | + | sample1.txt | n/a | 1 | 9 | 52 | 52 | | sample2.c | 5 | 11 | 33 | 219 | 219 | - | sample2.md | 0 | 1 | 8 | 53 | 53 | - | sample2.txt | 0 | 2 | 13 | 75 | 75 | + | sample2.md | n/a | 1 | 8 | 53 | 53 | + | sample2.txt | n/a | 2 | 13 | 75 | 75 | o--------------------------o-----------o-----------o-----------o-----------o-----------o Summary: o-------- Language --------o--- LLC ---o--- PHL ---o--- WRD ---o--- CHR ---o--- SZE ---o - | Plain Text | 0 | 3 | 22 | 127 | 127 | - | Markdown | 0 | 2 | 16 | 105 | 105 | + | Plain Text | n/a | 3 | 22 | 127 | 127 | + | Markdown | n/a | 2 | 16 | 105 | 105 | | C | 9 | 21 | 62 | 399 | 399 | | Java | 7 | 25 | 72 | 519 | 519 | o==========================o===========o===========o===========o===========o===========o diff --git a/src/scount/tests/functionality/res/expected/mixedWithSyntaxError.txt b/src/scount/tests/functionality/res/expected/mixedWithSyntaxError.txt index 34dcb42..72845db 100644 --- a/src/scount/tests/functionality/res/expected/mixedWithSyntaxError.txt +++ b/src/scount/tests/functionality/res/expected/mixedWithSyntaxError.txt @@ -3,13 +3,13 @@ Scanned files: 3 o---------- File ----------o--- LLC ---o--- PHL ---o--- WRD ---o--- CHR ---o--- SZE ---o | CorrectFile.java | 5 | 12 | 33 | 273 | 273 | - | correct_file.txt | 0 | 1 | 20 | 109 | 109 | + | correct_file.txt | n/a | 1 | 20 | 109 | 109 | o--------------------------o-----------o-----------o-----------o-----------o-----------o Summary: o-------- Language --------o--- LLC ---o--- PHL ---o--- WRD ---o--- CHR ---o--- SZE ---o - | Plain Text | 0 | 1 | 20 | 109 | 109 | + | Plain Text | n/a | 1 | 20 | 109 | 109 | | Java | 5 | 12 | 33 | 273 | 273 | o==========================o===========o===========o===========o===========o===========o | Total: | 5 | 13 | 53 | 382 | 382 | diff --git a/src/scount/tests/functionality/res/expected/output_multiple_files_no_llc.txt b/src/scount/tests/functionality/res/expected/output_multiple_files_no_llc.txt new file mode 100644 index 0000000..a62abe9 --- /dev/null +++ b/src/scount/tests/functionality/res/expected/output_multiple_files_no_llc.txt @@ -0,0 +1,21 @@ +Directory: txt +Scanned files: 6 + + o---------- File ----------o--- LLC ---o--- PHL ---o--- WRD ---o--- CHR ---o--- SZE ---o + | 1sample1.txt | n/a | 1 | 2 | 26 | 26 | + | 1sample2.txt | n/a | 1 | 2 | 26 | 26 | + | 1sample3.txt | n/a | 1 | 2 | 26 | 26 | + | 2sample1.txt | n/a | 1 | 2 | 31 | 31 | + | 2sample2.txt | n/a | 1 | 2 | 31 | 31 | + | 3sample1.txt | n/a | 1 | 2 | 36 | 36 | + o--------------------------o-----------o-----------o-----------o-----------o-----------o + +Summary: + + o-------- Language --------o--- LLC ---o--- PHL ---o--- WRD ---o--- CHR ---o--- SZE ---o + | Plain Text | n/a | 6 | 12 | 176 | 176 | + o==========================o===========o===========o===========o===========o===========o + | Total: | n/a | 6 | 12 | 176 | 176 | + o==========================o===========o===========o===========o===========o===========o + + diff --git a/src/scount/tests/functionality/res/expected/output_single_file_no_llc.txt b/src/scount/tests/functionality/res/expected/output_single_file_no_llc.txt new file mode 100644 index 0000000..d882727 --- /dev/null +++ b/src/scount/tests/functionality/res/expected/output_single_file_no_llc.txt @@ -0,0 +1,8 @@ +File: sample1.md + + Logical Lines of Code (LLC): n/a + Physical Lines (PHL): 1 + Words (WRD): 8 + Characters (CHR): 52 + Source Size in Bytes (SZE): 52 + diff --git a/src/scount/tests/functionality/sh/test_scount.sh b/src/scount/tests/functionality/sh/test_scount.sh index a85a0a3..2dac77a 100644 --- a/src/scount/tests/functionality/sh/test_scount.sh +++ b/src/scount/tests/functionality/sh/test_scount.sh @@ -45,6 +45,20 @@ function test_scount_with_relative_directory_input() { assert_stderr_is_empty; } +function test_scount_prints_correct_output_for_single_file_no_llc() { + run_app "${TEST_RES_DIR}/mixed/sample1.md"; + assert_exit_status $EXIT_SUCCESS; + assert_stdout_equals_file "expected/output_single_file_no_llc.txt"; + assert_stderr_is_empty; +} + +function test_scount_prints_correct_output_for_multiple_files_no_llc() { + run_app "${TEST_PROJECT_DIR}/src/lib/tests/res/txt"; + assert_exit_status $EXIT_SUCCESS; + assert_stdout_equals_file "expected/output_multiple_files_no_llc.txt"; + assert_stderr_is_empty; +} + function test_scount_prints_annotated_source_code() { run_app --annotate-counts "${TEST_PROJECT_DIR}/src/lib/tests/res/java/Sample.java"; assert_exit_status $EXIT_SUCCESS; diff --git a/src/scount/tests/unit/c/test_print.c b/src/scount/tests/unit/c/test_print.c index 063635f..ce07efe 100644 --- a/src/scount/tests/unit/c/test_print.c +++ b/src/scount/tests/unit/c/test_print.c @@ -15,6 +15,7 @@ */ #include +#include #include #include @@ -50,6 +51,7 @@ static RcnCountStatistics* mkStats( stats->count.results[i].words = words; stats->count.results[i].characters = characters; stats->count.results[i].sourceSize = sourceSize; + stats->count.results[i].hasLogicalLines = true; } stats->logicalLines[RCN_LANG_JAVA] = logical; stats->physicalLines[RCN_LANG_JAVA] = physical; From c1b423b1792caa325aa07431a11f8784714e1ba7 Mon Sep 17 00:00:00 2001 From: kilo52 Date: Fri, 30 Jan 2026 15:22:48 +0100 Subject: [PATCH 2/2] Refactored logic for printing LLC count into separate function. Signed-off-by: kilo52 --- src/scount/c/print.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/scount/c/print.c b/src/scount/c/print.c index 8d08976..7bc9993 100644 --- a/src/scount/c/print.c +++ b/src/scount/c/print.c @@ -250,6 +250,18 @@ static void prNotApplicable(PrintBuffer* buffer) { prRpt(buffer, ' ', pad); } +static void prLogicalLineCount( + PrintBuffer* buffer, + bool hasLogicalLines, + RcnCount value +) { + if (hasLogicalLines) { + prCnt(buffer, value, WIDTH_COL1); + } else { + prNotApplicable(buffer); + } +} + static void prHeaderCell(PrintBuffer* buffer, const char* label, int width) { assert(label != NULL); const int length = (int) strlen(label); @@ -401,11 +413,7 @@ static void prFileRowData( prChr(buffer, ' '); prChr(buffer, TABLE_BORDER_VERTICAL_NORMAL); prChr(buffer, ' '); - if (res->hasLogicalLines) { - prCnt(buffer, res->logicalLines, WIDTH_COL1); - } else { - prNotApplicable(buffer); - } + prLogicalLineCount(buffer, res->hasLogicalLines, res->logicalLines); prChr(buffer, ' '); prChr(buffer, TABLE_BORDER_VERTICAL_NORMAL); prChr(buffer, ' '); @@ -499,11 +507,7 @@ static void prSummaryRows( prChr(buffer, ' '); prChr(buffer, TABLE_BORDER_VERTICAL_NORMAL); prChr(buffer, ' '); - if (hasLogicalLines) { - prCnt(buffer, stats->logicalLines[frmt], WIDTH_COL1); - } else { - prNotApplicable(buffer); - } + prLogicalLineCount(buffer, hasLogicalLines, stats->logicalLines[frmt]); prChr(buffer, ' '); prChr(buffer, TABLE_BORDER_VERTICAL_NORMAL); prChr(buffer, ' '); @@ -534,11 +538,11 @@ static void prTotalsRow(PrintBuffer* buffer, const RcnCountStatistics* stats) { prChr(buffer, ' '); prChr(buffer, TABLE_BORDER_VERTICAL_NORMAL); prChr(buffer, ' '); - if (hasAnyLogicalLines(stats)) { - prCnt(buffer, stats->totalLogicalLines, WIDTH_COL1); - } else { - prNotApplicable(buffer); - } + prLogicalLineCount( + buffer, + hasAnyLogicalLines(stats), + stats->totalLogicalLines + ); prChr(buffer, ' '); prChr(buffer, TABLE_BORDER_VERTICAL_NORMAL); prChr(buffer, ' ');