Skip to content

Commit

Permalink
Merge pull request #12 from web-of-things-open-source/feat/fetch
Browse files Browse the repository at this point in the history
Feat/fetch
  • Loading branch information
hadjian authored Nov 15, 2023
2 parents f6b6cd7 + 8a98109 commit 3afaf3e
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 2 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ tm-catalog-cli
# Go workspace file
go.work

# IntelliJ
.idea/

# Emacs tmp files
*~

# tm-catalog-cli configuration
config.json
48 changes: 48 additions & 0 deletions cmd/fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package cmd

import (
"fmt"
"log/slog"
"os"

"github.com/spf13/cobra"
"github.com/web-of-things-open-source/tm-catalog-cli/internal/commands"
"github.com/web-of-things-open-source/tm-catalog-cli/internal/remotes"
)

var fetchCmd = &cobra.Command{
Use: "fetch NAME[:SEMVER|DIGEST]",
Short: "Fetches the TM by name",
Long: "Fetches TM by name, optionally accepting semantic version or digest",
Args: cobra.ExactArgs(1),
Run: executeFetch,
}

func init() {
rootCmd.AddCommand(fetchCmd)
fetchCmd.Flags().StringP("remote", "r", "", "use named remote instead of default")
}

func executeFetch(cmd *cobra.Command, args []string) {
log := slog.Default()

remoteName := cmd.Flag("remote").Value.String()
remote, err := remotes.Get(remoteName)
if err != nil {
log.Error(fmt.Sprintf("could not initialize a remote instance for %s. check config", remoteName), "error", err)
os.Exit(1)
}

fn := &commands.FetchName{}
err = fn.Parse(args[0])
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
thing, err := commands.FetchThingByName(fn, remote)
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
fmt.Println(string(thing))
}
4 changes: 2 additions & 2 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (
)

var listCmd = &cobra.Command{
Use: "list [pattern]",
Use: "list [PATTERN]",
Short: "List TMs in catalog",
Long: `List TMs and filter for pattern in all mandatory fields`,
Long: `List TMs and filter for PATTERN in all mandatory fields`,
Args: cobra.MaximumNArgs(1),
Run: listRemote,
}
Expand Down
185 changes: 185 additions & 0 deletions internal/commands/fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package commands

import (
"errors"
"fmt"
"log/slog"
"os"
"regexp"
"time"

"github.com/Masterminds/semver/v3"
"github.com/web-of-things-open-source/tm-catalog-cli/internal/model"
"github.com/web-of-things-open-source/tm-catalog-cli/internal/remotes"
)

type FetchName struct {
Name string
SemVerOrDigest string
}

func (fn *FetchName) Parse(fetchName string) error {
pattern := `^([^:]+)(:(.+))?$`

// Compile the regular expression
re := regexp.MustCompile(pattern)

// Find submatches in the input string
matches := re.FindStringSubmatch(fetchName)

// Check if there are enough submatches
if len(matches) < 2 {
msg := fmt.Sprintf("Invalid name format: %s - Must be NAME[:SEMVER|DIGEST]", fetchName)
slog.Default().Error(msg)
return fmt.Errorf(msg)
}

// Extract values from submatches
fn.Name = matches[1]
if len(matches) > 3 {
fn.SemVerOrDigest = matches[3]
}
return nil
}

func FetchThingByName(fn *FetchName, remote remotes.Remote) ([]byte, error) {
tocThing, err := remote.Versions(fn.Name)
if err != nil {
fmt.Errorf(err.Error())
os.Exit(1)
}

var id string
var thing []byte
// Just the name specified: fetch most recent
if len(fn.SemVerOrDigest) == 0 {
id, err = findMostRecentVersion(tocThing.Versions)
if err != nil {
return nil, err
}
} else if fetchVersion, err := semver.NewVersion(fn.SemVerOrDigest); err == nil {
id, err = findMostRecentTimeStamp(tocThing.Versions, fetchVersion)
if err != nil {
return nil, err
}
} else {
id, err = findDigest(tocThing.Versions, fn.SemVerOrDigest)
if err != nil {
return nil, err
}
}

// TODO: how to know if it is official?
tmid, err := model.ParseTMID(id, false)
thing, err = remote.Fetch(tmid)
if err != nil {
msg := fmt.Sprintf("No thing model found for %s", fn)
slog.Default().Error(msg)
return nil, errors.New(msg)
}
return thing, nil
}

func findMostRecentVersion(versions []model.TocVersion) (id string, err error) {
log := slog.Default()
if len(versions) == 0 {
msg := "No versions found"
log.Error(msg)
return "", errors.New(msg)
}

latestVersion, _ := semver.NewVersion("v0.0.0")
var latestTimeStamp time.Time

for _, version := range versions {
// TODO: use StrictNewVersion
currentVersion, err := semver.NewVersion(version.Version.Model)
if err != nil {
log.Error(err.Error())
return "", err
}
if currentVersion.GreaterThan(latestVersion) {
latestVersion = currentVersion
latestTimeStamp, err = time.Parse(pseudoVersionTimestampFormat, version.TimeStamp)
id = version.ID
continue
}
if currentVersion.Equal(latestVersion) {
currentTimeStamp, err := time.Parse(pseudoVersionTimestampFormat, version.TimeStamp)
if err != nil {
log.Error(err.Error())
return "", err
}
if currentTimeStamp.After(latestTimeStamp) {
latestTimeStamp = currentTimeStamp
id = version.ID
continue
}
}
}
return id, nil
}

func findMostRecentTimeStamp(versions []model.TocVersion, ver *semver.Version) (id string, err error) {
log := slog.Default()
if len(versions) == 0 {
msg := "No versions found"
log.Error(msg)
return "", errors.New(msg)
}
var latestTimeStamp time.Time

for _, version := range versions {
// TODO: use StrictNewVersion
currentVersion, err := semver.NewVersion(version.Version.Model)
if err != nil {
log.Error(err.Error())
return "", err
}

if !currentVersion.Equal(ver) {
continue
}
currentTimeStamp, err := time.Parse(pseudoVersionTimestampFormat, version.TimeStamp)
if err != nil {
log.Error(err.Error())
return "", err
}
if currentTimeStamp.After(latestTimeStamp) {
latestTimeStamp = currentTimeStamp
id = version.ID
continue
}
}
if len(id) == 0 {
msg := fmt.Sprintf("No version %s found", ver.String())
log.Error(msg)
return "", errors.New(msg)
}
return id, nil
}

func findDigest(versions []model.TocVersion, digest string) (id string, err error) {
log := slog.Default()
if len(versions) == 0 {
msg := "No versions found"
log.Error(msg)
return "", errors.New(msg)
}

digest = prep(digest)
for _, version := range versions {
// TODO: how to know if it is official?
tmid, err := model.ParseTMID(version.ID, false)
if err != nil {
log.Error(fmt.Sprintf("Unable to parse TMID from %s", version.ID))
return "", err
}
if tmid.Version.Hash == digest {
return version.ID, nil
}
}
msg := fmt.Sprintf("No thing model found for digest %s", digest)
log.Error(msg)
return "", errors.New(msg)
}

0 comments on commit 3afaf3e

Please sign in to comment.