Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"os"
"time"

"github.com/experimental-software/logbook2/core"
"github.com/experimental-software/logbook2/logging"
Expand All @@ -16,7 +17,7 @@ var addCmd = &cobra.Command{

Run: func(cmd *cobra.Command, args []string) {
title := args[0]
result, err := core.AddLogEntry(configuration.LogDirectory, title)
result, err := core.AddLogEntry(configuration.LogDirectory, title, time.Now())
if err != nil {
logging.Error("Failed to create log entry", err)
os.Exit(1)
Expand Down
28 changes: 26 additions & 2 deletions cmd/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,44 @@ import (
"fmt"
"os"
"strings"
"time"

"github.com/aquasecurity/table"
"github.com/experimental-software/logbook2/core"
"github.com/experimental-software/logbook2/utils"
"github.com/spf13/cobra"
)

var FromParameter string
var ToParameter string

var searchCmd = &cobra.Command{
Use: "search [flags] search_term",
Aliases: []string{"s"},
Short: "Search for logbook entries",

PreRunE: func(cmd *cobra.Command, args []string) error {
_, err := time.Parse(utils.RFC3339date, FromParameter)
if err != nil {
return fmt.Errorf("unexpected format for --from flag")
}
_, err = time.Parse(utils.RFC3339date, ToParameter)
if err != nil {
return fmt.Errorf("unexpected format for --to flag")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
searchTerm := ""
if len(args) > 0 {
searchTerm = args[0]
}
logEntries := core.Search(configuration.LogDirectory, searchTerm)

from, _ := time.Parse(utils.RFC3339date, FromParameter)
to, _ := time.Parse(utils.RFC3339date, ToParameter)

logEntries := core.Search(
configuration.LogDirectory, searchTerm, from, to,
)

outputFormat, err := cmd.Flags().GetString("output-format")
if err != nil {
Expand Down Expand Up @@ -61,6 +82,9 @@ var searchCmd = &cobra.Command{

func init() {
flags := searchCmd.Flags()
flags.StringVarP(&FromParameter, "from", "f", "1970-01-01", "RFC 3339 formatted date for the earliest considered logbook entry.")
flags.StringVarP(&ToParameter, "to", "t", "2100-01-01", "RFC 3339 formatted date for the latest considered logbook entry.")

flags.VarP(StringChoice([]string{
"table", "list", "json",
}), "output-format", "o", "The format in which the log entries are printed to the terminal.\n[table (default), list, json]")
Expand Down
28 changes: 15 additions & 13 deletions core/adding.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,24 @@ import (
"time"
)

func AddLogEntry(baseDirectory, title string) (LogbookEntry, error) {
currentTime := time.Now()
var epoc, _ = time.Parse("2006-01-02", "1970-01-01")
var nextCentury, _ = time.Parse("2006-01-02", "2100-01-01")

func AddLogEntry(baseDirectory, title string, dateTime time.Time) (LogbookEntry, error) {
slug := slugify(title)
dateTime := fmt.Sprintf("%d-0%d-0%dT0%d:0%d",
currentTime.Year(),
currentTime.Month(),
currentTime.Day(),
currentTime.Hour(),
currentTime.Minute(),
formattedDateTime := fmt.Sprintf("%d-0%d-0%dT0%d:0%d",
dateTime.Year(),
dateTime.Month(),
dateTime.Day(),
dateTime.Hour(),
dateTime.Minute(),
)

logDirectoryPath := filepath.Join(baseDirectory,
fmt.Sprintf("%d", currentTime.Year()),
fmt.Sprintf("%02d", currentTime.Month()),
fmt.Sprintf("%02d", currentTime.Day()),
fmt.Sprintf("%02d.%02d_%s", currentTime.Hour(), currentTime.Minute(), slug),
fmt.Sprintf("%d", dateTime.Year()),
fmt.Sprintf("%02d", dateTime.Month()),
fmt.Sprintf("%02d", dateTime.Day()),
fmt.Sprintf("%02d.%02d_%s", dateTime.Hour(), dateTime.Minute(), slug),
)
err := os.MkdirAll(logDirectoryPath, 0777)
if err != nil {
Expand All @@ -37,7 +39,7 @@ func AddLogEntry(baseDirectory, title string) (LogbookEntry, error) {
return LogbookEntry{}, err
}

return LogbookEntry{DateTime: dateTime, Title: title, Directory: logDirectoryPath}, nil
return LogbookEntry{DateTime: formattedDateTime, Title: title, Directory: logDirectoryPath}, nil
}

func slugify(s string) string {
Expand Down
5 changes: 3 additions & 2 deletions core/adding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package core
import (
"os"
"testing"
"time"
)

func TestAddLogEntry(t *testing.T) {
Expand All @@ -15,7 +16,7 @@ func TestAddLogEntry(t *testing.T) {
defer os.RemoveAll(tempDir)

// Act
entry, err := AddLogEntry(tempDir, "This is a new log entry")
entry, err := AddLogEntry(tempDir, "This is a new log entry", time.Now())

// Assert
if err != nil {
Expand All @@ -24,7 +25,7 @@ func TestAddLogEntry(t *testing.T) {
if entry.Title != "This is a new log entry" {
t.Errorf("AddLogEntry returned wrong title")
}
allLogEntries := Search(tempDir, "")
allLogEntries := Search(tempDir, "", epoc, nextCentury)
if len(allLogEntries) != 1 {
t.Errorf("AddLogEntry returned wrong number of entries")
}
Expand Down
19 changes: 10 additions & 9 deletions core/management_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"testing"
"time"

"github.com/experimental-software/logbook2/config"
)
Expand All @@ -18,11 +19,11 @@ func Test_Delete_happy_path(t *testing.T) {
_ = os.RemoveAll(archiveBaseDir)
}(logBaseDir)

logEntry, err := AddLogEntry(logBaseDir, "Log entry for archive test")
logEntry, err := AddLogEntry(logBaseDir, "Log entry for archive test", time.Now())
if err != nil {
t.Fatal(err)
}
searchResultForArchiveBaseDir := Search(logBaseDir, "")
searchResultForArchiveBaseDir := Search(logBaseDir, "", epoc, nextCentury)
if len(searchResultForArchiveBaseDir) != 1 {
t.Fatal("Expected 1 search result")
}
Expand All @@ -34,7 +35,7 @@ func Test_Delete_happy_path(t *testing.T) {
}

// Assert
searchResultForLogBaseDir := Search(logBaseDir, "")
searchResultForLogBaseDir := Search(logBaseDir, "", epoc, nextCentury)
if len(searchResultForLogBaseDir) != 0 {
t.Fatal("Expected empty search result")
}
Expand All @@ -49,7 +50,7 @@ func Test_Archive_happy_path(t *testing.T) {
_ = os.RemoveAll(archiveBaseDir)
}(logBaseDir)

logEntry, err := AddLogEntry(logBaseDir, "Log entry for archive test")
logEntry, err := AddLogEntry(logBaseDir, "Log entry for archive test", time.Now())
if err != nil {
t.Fatal(err)
}
Expand All @@ -66,11 +67,11 @@ func Test_Archive_happy_path(t *testing.T) {
}

// Assert
searchResultForLogBaseDir := Search(logBaseDir, "")
searchResultForLogBaseDir := Search(logBaseDir, "", epoc, nextCentury)
if len(searchResultForLogBaseDir) != 0 {
t.Fatal("Expected empty search result")
}
searchResultForArchiveBaseDir := Search(archiveBaseDir, "")
searchResultForArchiveBaseDir := Search(archiveBaseDir, "", epoc, nextCentury)
if len(searchResultForArchiveBaseDir) != 1 {
t.Fatal("Expected 1 search result")
}
Expand All @@ -85,7 +86,7 @@ func Test_Archive_path_in_subdirectory(t *testing.T) {
_ = os.RemoveAll(archiveBaseDir)
}(logBaseDir)

logEntry, err := AddLogEntry(logBaseDir, "Log entry for archive test")
logEntry, err := AddLogEntry(logBaseDir, "Log entry for archive test", time.Now())
if err != nil {
t.Fatal(err)
}
Expand All @@ -103,11 +104,11 @@ func Test_Archive_path_in_subdirectory(t *testing.T) {
}

// Assert
searchResultForLogBaseDir := Search(logBaseDir, "")
searchResultForLogBaseDir := Search(logBaseDir, "", epoc, nextCentury)
if len(searchResultForLogBaseDir) != 0 {
t.Fatal("Expected empty search result")
}
searchResultForArchiveBaseDir := Search(archiveBaseDir, "")
searchResultForArchiveBaseDir := Search(archiveBaseDir, "", epoc, nextCentury)
if len(searchResultForArchiveBaseDir) != 1 {
t.Fatal("Expected 1 search result")
}
Expand Down
10 changes: 9 additions & 1 deletion core/searching.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import (
"path/filepath"
"regexp"
"strings"
"time"

"github.com/experimental-software/logbook2/logging"
)

var searchPathPattern = regexp.MustCompile(`.*[/\\](\d{4})[/\\](\d{2})[/\\](\d{2})[/\\](\d{2})\.(\d{2})_.*`)

func Search(baseDirectory, searchTerm string) []LogbookEntry {
func Search(baseDirectory, searchTerm string, from time.Time, to time.Time) []LogbookEntry {
var result = make([]LogbookEntry, 0)
err := filepath.Walk(baseDirectory,
func(path string, info os.FileInfo, err error) error {
Expand All @@ -30,6 +31,9 @@ func Search(baseDirectory, searchTerm string) []LogbookEntry {
pathDatetimeMatch[4],
pathDatetimeMatch[5],
)
if !isInRequestedTimeRange(logDatetime, from, to) {
return nil
}

logFileBytes, err := os.ReadFile(path)
if err != nil {
Expand All @@ -56,6 +60,10 @@ func Search(baseDirectory, searchTerm string) []LogbookEntry {
return result
}

func isInRequestedTimeRange(datetime string, from time.Time, to time.Time) bool {
return datetime >= from.Format(time.RFC3339) && datetime <= to.Format(time.RFC3339)
}

func isLogEntryFile(path string) bool {
pathParts := strings.Split(path, string(os.PathSeparator))
lastPathPart := pathParts[len(pathParts)-1]
Expand Down
59 changes: 53 additions & 6 deletions core/searching_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,74 @@ package core
import (
"fmt"
"testing"
"time"

"github.com/experimental-software/logbook2/utils"
)

func TestSearchWithoutSearchTerm(t *testing.T) {
result := Search("./t/2026/01", "")
func Test_Search_without_search_term(t *testing.T) {
result := Search("./t/2026/01", "", epoc, nextCentury)
if len(result) != 2 {
t.Errorf("Expected two search results but found %v", len(result))
}
}

func TestSearchWithSearchTerm(t *testing.T) {
result := Search("./t/2026/01", "ANOTHER")
func Test_Search_with_search_term(t *testing.T) {
result := Search("./t/2026/01", "ANOTHER", epoc, nextCentury)
if len(result) != 1 {
t.Errorf("Expected only one search result but found %v", len(result))
}
fmt.Println(result)
}

func TestIgnoreUnexpectedData(t *testing.T) {
result := Search("./t/2023", "")
func Test_Search_ignore_unexpected_data(t *testing.T) {
result := Search("./t/2023", "", epoc, nextCentury)
if len(result) != 0 {
t.Errorf("Expected no search result but found %v", len(result))
}
fmt.Println(result)
}

func Test_isInRequestedTimeRange(t *testing.T) {

testCases := []struct {
name string
dateTime string
from string
to string
expected bool
}{
{
name: "happy path",
dateTime: "2025-12-12T20:25",
from: "1970-01-01",
to: "2100-01-01",
expected: true,
},
{
name: "ignore too early",
dateTime: "2025-12-12T20:25",
from: "2025-12-13",
to: "2100-01-01",
expected: false,
},
{
name: "ignore too late",
dateTime: "2025-12-12T20:25",
from: "1970-01-01",
to: "2025-12-11",
expected: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
from, _ := time.Parse(utils.RFC3339date, tc.from)
to, _ := time.Parse(utils.RFC3339date, tc.to)
result := isInRequestedTimeRange(tc.dateTime, from, to)
if result != tc.expected {
t.Errorf("expected: %v, got: %v", tc.expected, result)
}
})
}
}
3 changes: 3 additions & 0 deletions utils/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package utils

const RFC3339date = "2006-01-02"