Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ If you encounter any issues or have questions, feel free to open an issue on [Gi
## 🙏 Acknowledgments

Special thanks to,

- the Go community, for their invaluable resources and inspiration.
- [MelkeyDev](https://github.com/MelkeyDev), for the inspiration to build this tool.
- [Cobra CLI](https://github.com/spf13/cobra), for helping to build command-line interface.
- [Charm\_](https://github.com/charmbracelet), for building beautiful and interactive CLI components.
- [lic](https://github.com/kushvinth/lic), for inspiring the license generation feature.
89 changes: 88 additions & 1 deletion cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"errors"
"fmt"
"os"
"time"

"github.com/charmbracelet/huh"
"github.com/charmbracelet/huh/spinner"
"github.com/spf13/cobra"
app "github.com/vg006/vgo/internal"
asset "github.com/vg006/vgo/internal/assets"
"github.com/vg006/vgo/internal/license"
)

var initCmd = &cobra.Command{
Expand All @@ -18,6 +20,55 @@ var initCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
var p app.Project
accessible, _ := cmd.Flags().GetBool("accessible")

// Fetch licenses from GitHub API
var licenses []license.GitHubLicense
var licenseOptions []huh.Option[string]

err := spinner.
New().
Title("Loading licenses from GitHub...").
Action(func() {
var fetchErr error
licenses, fetchErr = license.FetchLicenses()
if fetchErr != nil {
// If fetch fails, provide some basic options
licenses = []license.GitHubLicense{
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the keys are definite and known, we don't need a fetch call here and better skip it.

{Key: "mit", Name: "MIT License"},
{Key: "apache-2.0", Name: "Apache License 2.0"},
{Key: "gpl-3.0", Name: "GNU General Public License v3.0"},
}
}
// Convert licenses to huh options
licenseOptions = make([]huh.Option[string], 0, len(licenses)+1)
licenseOptions = append(licenseOptions, huh.NewOption("None", "none"))
for _, lic := range licenses {
licenseOptions = append(licenseOptions, huh.NewOption(lic.Name, lic.Key))
}
}).
Style(asset.Text).
Accessible(accessible).
Run()

if err != nil {
// Fallback to basic options if spinner fails
licenseOptions = []huh.Option[string]{
huh.NewOption("None", "none"),
huh.NewOption("MIT License", "mit"),
huh.NewOption("Apache License 2.0", "apache-2.0"),
huh.NewOption("GNU General Public License v3.0", "gpl-3.0"),
}
}

// Get git username for default author
gitUsername := license.GetGitUsername()
if gitUsername == "" {
gitUsername = "Your Name"
}

// Set default year
p.Year = fmt.Sprintf("%d", time.Now().Year())

form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Expand Down Expand Up @@ -87,13 +138,49 @@ var initCmd = &cobra.Command{
huh.NewOption("MongoDB", "mongodb"),
),
),
huh.NewGroup(
huh.NewSelect[string]().
Value(&p.License).
Title("License").
Description("Select a license for the project").
Options(licenseOptions...),
),
huh.NewGroup(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conditionally can have this input group

huh.NewInput().
Value(&p.Author).
Title("Author").
Description("Enter the author name for the license").
Placeholder(gitUsername).
Validate(func(s string) error {
if s == "" && p.License != "none" {
p.Author = gitUsername
}
return nil
}),
).WithHideFunc(func() bool {
return p.License == "none"
}),
huh.NewGroup(
huh.NewInput().
Value(&p.Year).
Title("Year").
Description("Enter the year for the license").
Validate(func(s string) error {
if s == "" {
p.Year = fmt.Sprintf("%d", time.Now().Year())
}
return nil
}),
).WithHideFunc(func() bool {
return p.License == "none"
}),
).
WithAccessible(accessible).
WithTheme(asset.SetTheme())

fmt.Println(asset.VgoLogo)

err := form.Run()
err = form.Run()
if err != nil {
fmt.Println(asset.Text.Foreground(asset.Red).
Render(fmt.Sprintf("%s Hey! Why stopped?", asset.EmojiConfused)))
Expand Down
3 changes: 3 additions & 0 deletions internal/app.d.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ type Project struct {
ModName string
FrameWork string
Database string
License string
Author string
Year string
}

type AppError struct {
Expand Down
15 changes: 15 additions & 0 deletions internal/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sync"
"text/template"

"github.com/vg006/vgo/internal/license"
tmpl "github.com/vg006/vgo/internal/templates"
)

Expand Down Expand Up @@ -83,6 +84,20 @@ func (p *Project) ScaffoldProject() error {
errChan <- err
}

// Creates LICENSE file if a license is selected
if p.License != "" && p.License != "none" {
licenseContent, err := license.GetLicenseContent(p.License)
if err != nil {
errChan <- err
} else {
renderedLicense := license.RenderLicense(licenseContent, p.Author, p.Year)
err = os.WriteFile("LICENSE", []byte(renderedLicense), 0644)
if err != nil {
errChan <- err
}
}
}

// Creates other project directories
// -----------------------------------------------------------------
// Creates the cmd directory
Expand Down
106 changes: 106 additions & 0 deletions internal/license/license.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package license

import (
"encoding/json"
"fmt"
"io"
"net/http"
"os/exec"
"strings"
"time"
)

type GitHubLicense struct {
Key string `json:"key"`
Name string `json:"name"`
Body string `json:"body"`
}

// GetGitUsername retrieves the git user.name from global config
func GetGitUsername() string {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

cmd := exec.Command("git", "config", "--global", "--get", "user.name")
output, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(output))
}

// FetchLicenses retrieves the list of available licenses from GitHub API
func FetchLicenses() ([]GitHubLicense, error) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove this

client := &http.Client{
Timeout: 10 * time.Second,
}

resp, err := client.Get("https://api.github.com/licenses")
if err != nil {
return nil, fmt.Errorf("failed to fetch licenses: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("github API returned status: %d", resp.StatusCode)
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}

var licenses []GitHubLicense
err = json.Unmarshal(body, &licenses)
if err != nil {
return nil, fmt.Errorf("failed to parse licenses: %w", err)
}

return licenses, nil
}

// GetLicenseContent retrieves the full license content from GitHub API
func GetLicenseContent(key string) (string, error) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove this

client := &http.Client{
Timeout: 10 * time.Second,
}

url := fmt.Sprintf("https://api.github.com/licenses/%s", key)
resp, err := client.Get(url)
if err != nil {
return "", fmt.Errorf("failed to fetch license content: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("github API returned status: %d", resp.StatusCode)
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response: %w", err)
}

var license GitHubLicense
err = json.Unmarshal(body, &license)
if err != nil {
return "", fmt.Errorf("failed to parse license: %w", err)
}

return license.Body, nil
}

// RenderLicense replaces placeholders in license content with actual values
func RenderLicense(content, author, year string) string {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

replacements := map[string]string{
"[year]": year,
"[fullname]": author,
"[yyyy]": year,
"[name of copyright owner]": author,
"[NAME OF COPYRIGHT OWNER]": author,
}

result := content
for placeholder, value := range replacements {
result = strings.ReplaceAll(result, placeholder, value)
}

return result
}