Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ feat: Add run command #5

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
11 changes: 11 additions & 0 deletions .nestri.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: "0.1"

games:
CyberPunk2047:
directory: /path/to/game
executable: CyberPunk2047.exe
gpu: 1
vendor: GPUidhere #(ex: vendor:N) e.g. nvidia:0 or amd:1
resolution:
height: 1080
width: 1920
95 changes: 95 additions & 0 deletions cmd/neofetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package cmd

import (
"fmt"
"strings"

"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/table"
"github.com/muesli/termenv"
"github.com/spf13/cobra"
)

var neoFetchCmd = &cobra.Command{
Use: "neofetch",
Short: "Show important system information",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
lipgloss.SetColorProfile(termenv.TrueColor)

// baseStyle := lipgloss.NewStyle().
// MarginTop(1).
// MarginRight(4).
// MarginBottom(1).
// MarginLeft(4)

var (
b strings.Builder
lines = strings.Split(art, "\n")
colors = []string{"#CC3D00", "#CC3D00"}
step = len(lines) / len(colors)
)

for i, l := range lines {
n := clamp(0, len(colors)-1, i/step)
b.WriteString(colorize(colors[n], l))
b.WriteRune('\n')
}

t := table.New().
Border(lipgloss.HiddenBorder()).BorderStyle(lipgloss.NewStyle().Width(3))
//TODO: show this specs
// info := &specs.Specs{}
// infoChan := make(chan specs.Specs, 1)
// var wg sync.WaitGroup
// wg.Add(1)
// go getSpecs(info, infoChan, &wg)
// wg.Wait()
// newInfo := <-infoChan

t.Row(b.String())

fmt.Print(t)

return nil
},
}

func init() {
rootCmd.AddCommand(neoFetchCmd)

// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// runCmd.PersistentFlags().String("foo", "", "A help for foo")

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// runCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

func colorize(c, s string) string {
return lipgloss.NewStyle().Foreground(lipgloss.Color(c)).Render(s)
}

func clamp(v, low, high int) int {
if high < low {
low, high = high, low
}
return min(high, max(low, v))
}

func min(a, b int) int {
if a < b {
return a
}
return b
}

func max(a, b int) int {
if a > b {
return a
}
return b
}
134 changes: 45 additions & 89 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,27 @@ import (
_ "embed"
"fmt"
"os"
"strings"
"sync"

"github.com/nestriness/cli/pkg/specs"

"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/table"
"github.com/muesli/termenv"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

//go:embed nestri.ascii
var art string

var cfgFile string

type GameConfig struct {
Directory string
Executable string
GPU int
Vendor string
Resolution struct {
Height int
Width int
}
}

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "nestri",
Expand All @@ -31,51 +38,6 @@ var rootCmd = &cobra.Command{
},
}

var neoFetchCmd = &cobra.Command{
Use: "neofetch",
Short: "Show important system information",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
lipgloss.SetColorProfile(termenv.TrueColor)

// baseStyle := lipgloss.NewStyle().
// MarginTop(1).
// MarginRight(4).
// MarginBottom(1).
// MarginLeft(4)

var (
b strings.Builder
lines = strings.Split(art, "\n")
colors = []string{"#CC3D00", "#CC3D00"}
step = len(lines) / len(colors)
)

for i, l := range lines {
n := clamp(0, len(colors)-1, i/step)
b.WriteString(colorize(colors[n], l))
b.WriteRune('\n')
}

t := table.New().
Border(lipgloss.HiddenBorder()).BorderStyle(lipgloss.NewStyle().Width(3))
//TODO: show this specs
// info := &specs.Specs{}
// infoChan := make(chan specs.Specs, 1)
// var wg sync.WaitGroup
// wg.Add(1)
// go getSpecs(info, infoChan, &wg)
// wg.Wait()
// newInfo := <-infoChan

t.Row(b.String())

fmt.Print(t)

return nil
},
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
Expand All @@ -86,55 +48,49 @@ func Execute() {
}

func init() {
cobra.OnInitialize(initConfig)
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
rootCmd.AddCommand(neoFetchCmd)

// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cli.yaml)")
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.nestri.yaml)")

// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

func colorize(c, s string) string {
return lipgloss.NewStyle().Foreground(lipgloss.Color(c)).Render(s)
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Search for config in the current directory
viper.AddConfigPath(".")
viper.SetConfigName(".nestri")
viper.SetConfigType("yaml")

// If not found in current directory, check in $HOME/.nestri/
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Find home directory.
home, err := os.UserHomeDir()
cobra.CheckErr(err)

// Search config in home directory with name ".nestri" (without extension).
viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName(".nestri")
}
}

func clamp(v, low, high int) int {
if high < low {
low, high = high, low
}
return min(high, max(low, v))
}

func min(a, b int) int {
if a < b {
return a
}
return b
}
viper.AutomaticEnv() // read in environment variables that match

func max(a, b int) int {
if a > b {
return a
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err != nil {
fmt.Fprintln(os.Stderr, "Could not find a config file in local directory or in $HOME directory:")
}
return b
}

func getSpecs(info *specs.Specs, infoChan chan specs.Specs, wg *sync.WaitGroup) {
defer wg.Done()
sys := specs.New()
// info.Userhost = getUserHostname()
// info.OS = getOSName()
// info.Kernel = getKernelVersion()
// info.Uptime = getUptime()
// info.Shell = getShell()
// info.CPU = getCPUName()
// info.RAM = getMemStats()
info.GPU, _ = sys.GetGPUInfo()
// info.SystemArch, _ = getSystemArch()
// info.DiskUsage, _ = getDiskUsage()
infoChan <- *info
}
90 changes: 90 additions & 0 deletions cmd/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package cmd

import (
"fmt"
"runtime"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// this is the "nestri run" subcommand, takes no arguments for now
var runCmd = &cobra.Command{
Use: "run",
Short: "Run a game using nestri",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if runtime.GOOS != "linux" {
//make sure os is linux
fmt.Println("This command is only supported on Linux.")
return nil
}

//The main job here is to:
//1. look for an exe in a certain directory,
//2. mount the directory inside the container
//3. Run the nestri docker container
//4. SSH into the container and set up everything
//5. Run the game
//6. Provide the URL to play or throw an error otherwise.

// The last argument is the game to run
game := args[len(args)-1]

// Load game configuration
var gameConfig GameConfig
if err := viper.UnmarshalKey(fmt.Sprintf("games.%s", game), &gameConfig); err != nil {
return fmt.Errorf("error parsing game configuration: %w", err)
}

flags := cmd.Flags()

if flags.Changed("directory") || flags.Changed("d") {
gameConfig.Directory, _ = flags.GetString("directory")
}
if flags.Changed("executable") || flags.Changed("x") {
gameConfig.Executable, _ = flags.GetString("executable")
}
if flags.Changed("gpu") {
gameConfig.GPU, _ = flags.GetInt("gpu")
}
if flags.Changed("vendor") || flags.Changed("v") {
gameConfig.Vendor, _ = flags.GetString("vendor")
}
if flags.Changed("height") || flags.Changed("H") {
gameConfig.Resolution.Height, _ = flags.GetInt("height")
}
if flags.Changed("width") || flags.Changed("W") {
gameConfig.Resolution.Width, _ = flags.GetInt("width")
}

fmt.Println("Game config:", gameConfig)

return nil
},
}

func init() {
rootCmd.AddCommand(runCmd)

runCmd.Flags().StringP("directory", "d", "", "Game directory")
runCmd.Flags().StringP("executable", "x", "", "Game executable")
runCmd.Flags().Int("gpu", 0, "GPU number")
runCmd.Flags().StringP("vendor", "v", "", "GPU vendor")
runCmd.Flags().IntP("height", "H", 1080, "Screen height")
runCmd.Flags().IntP("width", "W", 1920, "Screen width")

// viper.BindPFlag("directory", runCmd.Flags().Lookup("directory"))
// viper.BindPFlag("executable", runCmd.Flags().Lookup("executable"))
// viper.BindPFlag("gpu", runCmd.Flags().Lookup("gpu"))
// viper.BindPFlag("vendor", runCmd.Flags().Lookup("vendor"))
// viper.BindPFlag("resolution.height", runCmd.Flags().Lookup("height"))
// viper.BindPFlag("resolution.width", runCmd.Flags().Lookup("width"))

viper.BindPFlag("games.*.directory", runCmd.Flags().Lookup("directory"))
viper.BindPFlag("games.*.executable", runCmd.Flags().Lookup("executable"))
viper.BindPFlag("games.*.gpu", runCmd.Flags().Lookup("gpu"))
viper.BindPFlag("games.*.vendor", runCmd.Flags().Lookup("vendor"))
viper.BindPFlag("games.*.resolution.height", runCmd.Flags().Lookup("height"))
viper.BindPFlag("games.*.resolution.width", runCmd.Flags().Lookup("width"))
}
Loading