Skip to content

Commit

Permalink
v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
wujingquan committed Jun 26, 2024
0 parents commit 95e51c5
Show file tree
Hide file tree
Showing 17 changed files with 799 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.yml]
indent_style = space
indent_size = 2
7 changes: 7 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
* text=auto
.travis.yml export-ignore
.styleci.yml export-ignore
.scrutinizer.yml export-ignore
BACKERS.md export-ignore
CONTRIBUTING.md export-ignore
CHANGELOG.md export-ignore
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/.idea
/.vscode
cvm.exe
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 wujingquan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# CVM for Windows

Removing the hassle of changing Composer versions in the CLI on Windows.

This package has a much more niche use case than nvm does. When developing on Windows and using the integrated terminal, it's quite difficult to get those terminals to _actually_ listen to PATH changes.

This utility changes that.

## Installation

Download the latest CVM version from the [releases page](https://github.com/wujingquan/cvm/releases/latest) .

Create the folder `%UserProfile%\.cvm\bin` (e.g. `C:\Users\Example\.cvm\bin`) and drop the cvm exe in there. Add the folder to your PATH.

## Commands
```
cvm list
```
Will list out all the available Composer versions you have installed

```
cvm path
```
Will tell you what to put in your Path variable.

```
cvm use 2.7.7
```
> [!NOTE]
> Versions must have major.minor specified in the *use* command. If a .patch version is omitted, newest available patch version is chosen.
Will switch your currently active Composer version to Composer 2.7.7

```
cvm install 2.7
```
> [!NOTE]
> The install command will automatically determine the newest minor/patch versions if they are not specified
Will install Composer 2.7 at the latest patch.

## Thanks
- Based on [PVM](https://github.com/hjbdev/pvm)
22 changes: 22 additions & 0 deletions commands/help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package commands

import (
"fmt"
"wujingquan/cvm/theme"
)

func Help(notFoundError bool) {
theme.Title("cvm: Composer Version Manager")
theme.Info("Version 1.0")

if notFoundError {
theme.Error("Command not found")
}

fmt.Println("Available Commands:")
fmt.Println(" help")
fmt.Println(" install")
fmt.Println(" list")
fmt.Println(" path")
fmt.Println(" use")
}
200 changes: 200 additions & 0 deletions commands/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package commands

import (
"fmt"
"io"
"log"
"net/http"
"os"
"regexp"
"strings"
"wujingquan/cvm/common"
"wujingquan/cvm/theme"
)

type Version struct {
Major string
Minor string
Patch string
Url string
}

func Install(args []string) {
if len(args) < 2 {
theme.Error("You must specify a version to install.")
return
}

desiredVersionNumbers := common.GetVersion(args[1])

if desiredVersionNumbers == (common.Version{}) {
theme.Error("Invalid version specified")
return
}

// Get the desired version from the user input
desiredMajorVersion := desiredVersionNumbers.Major
desiredMinorVersion := desiredVersionNumbers.Minor
desiredPatchVersion := desiredVersionNumbers.Patch

// perform get request to https://getcomposer.org/download/
resp, err := http.Get("https://getcomposer.org/download/")
if err != nil {
log.Fatalln(err)
}
// We Read the response body on the line below.
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
// Convert the body to type string
sb := string(body)
re := regexp.MustCompile(`<a href="([a-zA-Z0-9./-]+composer.phar)"([a-zA-Z0-9\s./-]+)title="([a-zA-Z0-9\s./-]+)"([a-zA-Z0-9\s./-]+)aria-label="([a-zA-Z0-9\s./-]+)">([a-zA-Z0-9./-]+)<\/a>`)
matches := re.FindAllStringSubmatch(sb, -1)

versions := make([]Version, 0)

for _, match := range matches {
url := match[1]
name := match[6]

// regex match name
versionNumbers := common.GetVersion(name)

major := versionNumbers.Major
minor := versionNumbers.Minor
patch := versionNumbers.Patch

// push to versions
versions = append(versions, Version{
Major: major,
Minor: minor,
Patch: patch,
Url: url,
})
}

// find desired version
var desiredVersion Version

if desiredMajorVersion != "" && desiredMinorVersion != "" && desiredPatchVersion != "" {
desiredVersion = FindExactVersion(versions, desiredMajorVersion, desiredMinorVersion, desiredPatchVersion)
}

if desiredMajorVersion != "" && desiredMinorVersion != "" && desiredPatchVersion == "" {
desiredVersion = FindLatestPatch(versions, desiredMajorVersion, desiredMinorVersion)
}

if desiredMajorVersion != "" && desiredMinorVersion == "" && desiredPatchVersion == "" {
desiredVersion = FindLatestMinor(versions, desiredMajorVersion)
}

if desiredVersion == (Version{}) {
theme.Error("Could not find the desired version: " + args[1])
return
}

fmt.Println("Installing Composer " + desiredVersion.Major + "." + desiredVersion.Minor + "." + desiredVersion.Patch)

homeDir, err := os.UserHomeDir()

if err != nil {
log.Fatalln(err)
}

// check if .cvm folder exists
if _, err := os.Stat(homeDir + "/.cvm"); os.IsNotExist(err) {
theme.Info("Creating .cvm folder in home directory")
os.Mkdir(homeDir+"/.cvm", 0755)
}

// check if .cvm/versions folder exists
if _, err := os.Stat(homeDir + "/.cvm/versions"); os.IsNotExist(err) {
theme.Info("Creating .cvm/versions folder in home directory")
os.Mkdir(homeDir+"/.cvm/versions", 0755)
}

// check if .cvm/versions/[specified version path] folder exists
if _, err := os.Stat(homeDir + "/.cvm/versions/" + desiredVersion.Major + "." + desiredVersion.Minor + "." + desiredVersion.Patch); os.IsNotExist(err) {
theme.Info("Creating .cvm/versions/" + desiredVersion.Major + "." + desiredVersion.Minor + "." + desiredVersion.Patch + " folder in home directory")
os.Mkdir(homeDir+"/.cvm/versions/"+desiredVersion.Major+"."+desiredVersion.Minor+"."+desiredVersion.Patch, 0755)
}

theme.Info("Downloading")

// Get the data
downloadResponse, err := http.Get("https://getcomposer.org" + desiredVersion.Url)
if err != nil {
log.Fatalln(err)
}

defer downloadResponse.Body.Close()

// filename from url
filename := strings.Split(desiredVersion.Url, "/")[len(strings.Split(desiredVersion.Url, "/"))-1]

// check if composer already exists
if _, err := os.Stat(homeDir + "/.cvm/versions/" + desiredVersion.Major + "." + desiredVersion.Minor + "." + desiredVersion.Patch + "/" + filename); err == nil {
theme.Error("Composer " + desiredVersion.Major + "." + desiredVersion.Minor + "." + desiredVersion.Patch + " already exists")
return
}

// Create the file
out, err := os.Create(homeDir + "/.cvm/versions/" + desiredVersion.Major + "." + desiredVersion.Minor + "." + desiredVersion.Patch + "/" + filename)
if err != nil {
log.Fatalln(err)
}

// Write the body to file
_, err = io.Copy(out, downloadResponse.Body)

if err != nil {
out.Close()
log.Fatalln(err)
}

// Close the file
out.Close()

theme.Success("Finished installing Composer " + desiredVersion.Major + "." + desiredVersion.Minor + "." + desiredVersion.Patch)
}

func FindExactVersion(versions []Version, major string, minor string, patch string) Version {
for _, version := range versions {
if version.Major == major && version.Minor == minor && version.Patch == patch {
return version
}
}

return Version{}
}

func FindLatestPatch(versions []Version, major string, minor string) Version {
latestPatch := Version{}

for _, version := range versions {
if version.Major == major && version.Minor == minor {
if latestPatch.Patch == "" || version.Patch > latestPatch.Patch {
latestPatch = version
}
}
}

return latestPatch
}

func FindLatestMinor(versions []Version, major string) Version {
latestMinor := Version{}

for _, version := range versions {
if version.Major == major {
if latestMinor.Minor == "" || version.Minor > latestMinor.Minor {
if latestMinor.Patch == "" || version.Patch > latestMinor.Patch {
latestMinor = version
}
}
}
}

return latestMinor
}
43 changes: 43 additions & 0 deletions commands/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package commands

import (
"log"
"os"
"wujingquan/cvm/theme"

"github.com/fatih/color"
)

func List() {
// get users home dir
homeDir, err := os.UserHomeDir()

if err != nil {
log.Fatalln(err)
}

// check if .cvmf older exists
if _, err := os.Stat(homeDir + "/.cvm"); os.IsNotExist(err) {
theme.Error("No Composer versions installed")
return
}

// check if .cvm/versions folder exists
if _, err := os.Stat(homeDir + "/.cvm/versions"); os.IsNotExist(err) {
theme.Error("No Composer versions installed")
return
}

// get all folders in .cvm/versions
versions, err := os.ReadDir(homeDir + "/.cvm/versions")
if err != nil {
log.Fatalln(err)
}

theme.Title("Installed Composer versions")

// print all folders
for _, version := range versions {
color.White(" " + version.Name())
}
}
22 changes: 22 additions & 0 deletions commands/path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package commands

import (
"fmt"
"log"
"os"
"path/filepath"
"wujingquan/cvm/theme"
)

func Path() {
theme.Title("cvm: Composer Version Manager")

// get home dir
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatalln(err)
}

fmt.Println("Add the following directory to your PATH:")
fmt.Println(" " + filepath.Join(homeDir, ".cvm", "bin"))
}
Loading

0 comments on commit 95e51c5

Please sign in to comment.