Skip to content

Commit

Permalink
Fixes arduino#77 and adds a beautiful loading spinner 80s style 🌎
Browse files Browse the repository at this point in the history
  • Loading branch information
saniales authored and cmaglie committed Oct 10, 2017
1 parent e1faa83 commit 0854081
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 38 deletions.
140 changes: 102 additions & 38 deletions cmd/arduino_sketch.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,18 @@ import (
"os"
"path/filepath"
"strings"
"time"

"github.com/bcmi-labs/arduino-cli/auth"
"github.com/bcmi-labs/arduino-cli/create_client_helpers"
"github.com/bgentry/go-netrc/netrc"
"github.com/briandowns/spinner"
homedir "github.com/mitchellh/go-homedir"

"github.com/bcmi-labs/arduino-modules/sketches"

"github.com/bcmi-labs/arduino-cli/cmd/formatter"
"github.com/bcmi-labs/arduino-cli/cmd/output"
"github.com/bcmi-labs/arduino-cli/common"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -86,12 +89,58 @@ func executeSketchSyncCommand(cmd *cobra.Command, args []string) {
os.Exit(errCoreConfig)
}

priority := arduinoSketchSyncFlags.Priority

if priority == "ask-once" {
if !formatter.IsCurrentFormat("text") {
formatter.PrintErrorMessage("ask mode for this command is only supported using text format")
os.Exit(errBadCall)
}
firstAsk := true
for priority != "pull-remote" &&
priority != "push-local" &&
priority != "skip" {
if !firstAsk {
formatter.Print("Invalid option: " + priority)
}
formatter.Print("What should I do when I detect a conflict? [pull-remote | push-local | skip]")
fmt.Scanln(&priority)
firstAsk = false
}
}

//loader
isTextMode := formatter.IsCurrentFormat("text")

var loader *spinner.Spinner

if isTextMode {
loader = spinner.New(spinner.CharSets[39], 100*time.Millisecond)
loader.Prefix = "Syncing Sketches... "

loader.Start()
}

stopSpinner := func() {
if isTextMode {
loader.Stop()
}
}

startSpinner := func() {
if isTextMode {
loader.Start()
}
}

username, bearerToken, err := login()
if err != nil {
if GlobalFlags.Verbose == 0 {
formatter.PrintErrorMessage("Cannot login automatically: try arduino login the run again this command")
} else {
stopSpinner()
formatter.PrintError(err)
os.Exit(errNetwork)
}
}

Expand All @@ -101,13 +150,15 @@ func executeSketchSyncCommand(cmd *cobra.Command, args []string) {
tok := "Bearer " + bearerToken
resp, err := client.SearchSketches(context.Background(), createClient.SearchSketchesPath(), nil, &username, &tok)
if err != nil {
stopSpinner()
formatter.PrintErrorMessage("Cannot get create sketches, sync failed")
os.Exit(errNetwork)
}
defer resp.Body.Close()

onlineSketches, err := client.DecodeArduinoCreateSketches(resp)
if err != nil {
stopSpinner()
formatter.PrintErrorMessage("Cannot unmarshal response from create, sync failed")
os.Exit(errGeneric)
}
Expand All @@ -117,32 +168,25 @@ func executeSketchSyncCommand(cmd *cobra.Command, args []string) {
onlineSketchesMap[*item.Name] = item
}

priority := arduinoSketchSyncFlags.Priority
maxLength := len(sketchMap) + len(onlineSketchesMap)

if priority == "ask-once" {
if !formatter.IsCurrentFormat("text") {
formatter.PrintErrorMessage("ask mode for this command is only supported using text format")
os.Exit(errBadCall)
}
firstAsk := true
for priority != "pull-remote" &&
priority != "push-local" &&
priority != "skip" {
if !firstAsk {
formatter.Print("Invalid option: " + priority)
}
formatter.Print("What should I do when I detect a conflict? [pull-remote | push-local | skip]")
fmt.Scanln(&priority)
firstAsk = false
}
// create output result struct with empty arrays.
result := output.SketchSyncResult{
PushedSketches: make([]string, 0, maxLength),
PulledSketches: make([]string, 0, maxLength),
SkippedSketches: make([]string, 0, maxLength),
Errors: make([]output.SketchSyncError, 0, maxLength),
}

for _, item := range sketchMap {

itemOnline, hasConflict := onlineSketchesMap[item.Name]
if hasConflict {
item.ID = itemOnline.ID.String()
//solve conflicts
if priority == "ask-always" {
stopSpinner()

if !formatter.IsCurrentFormat("text") {
formatter.PrintErrorMessage("ask mode for this command is only supported using text format")
os.Exit(errBadCall)
Expand All @@ -158,43 +202,50 @@ func executeSketchSyncCommand(cmd *cobra.Command, args []string) {
fmt.Scanln(&priority)
firstAsk = false
}

startSpinner()
}
switch priority {
case "push-local":
if GlobalFlags.Verbose > 0 {
formatter.Print("pushing edits of sketch: " + item.Name)
}
err := editSketch(*item, sketchbook, bearerToken)
if err != nil {
formatter.PrintError(err)
result.Errors = append(result.Errors, output.SketchSyncError{
Sketch: item.Name,
Error: err,
})
} else {
result.PushedSketches = append(result.PushedSketches, item.Name)
}
break
case "pull-remote":
if GlobalFlags.Verbose > 0 {
formatter.Print("pulling " + item.Name)
}
err := pullSketch(itemOnline, sketchbook, bearerToken)
if err != nil {
formatter.PrintError(err)
result.Errors = append(result.Errors, output.SketchSyncError{
Sketch: item.Name,
Error: err,
})
} else {
result.PulledSketches = append(result.PulledSketches, item.Name)
}
break
case "skip":
if GlobalFlags.Verbose > 0 {
formatter.Print("skipping " + item.Name)
}
result.SkippedSketches = append(result.SkippedSketches, item.Name)
break
default:
priority = "skip"
if GlobalFlags.Verbose > 0 {
formatter.Print("Priority not recognized, using skipping by default")
formatter.Print("skipping " + item.Name)
}

result.SkippedSketches = append(result.SkippedSketches, item.Name)
}

} else { //only local, push
formatter.Print("pushing " + item.Name)
pushSketch(*item, sketchbook, bearerToken)
err := pushSketch(*item, sketchbook, bearerToken)
if err != nil {
result.Errors = append(result.Errors, output.SketchSyncError{
Sketch: item.Name,
Error: err,
})
} else {
result.PushedSketches = append(result.PushedSketches, item.Name)
}
}
}
for _, item := range onlineSketches.Sketches {
Expand All @@ -203,13 +254,26 @@ func executeSketchSyncCommand(cmd *cobra.Command, args []string) {
continue
}
//only online, pull
formatter.Print("pulling " + *item.Name)
err := pullSketch(item, sketchbook, bearerToken)
if err != nil {
formatter.PrintError(err)
result.Errors = append(result.Errors, output.SketchSyncError{
Sketch: *item.Name,
Error: err,
})
} else {
result.PulledSketches = append(result.PulledSketches, *item.Name)
}
}
formatter.PrintResult("Sync Completed") // Issue # : Provide output struct to print the result in a prettier way.

stopSpinner()

// for text mode, show full info (aka String()) only if verbose > 0.
// for other formats always print full info.
if GlobalFlags.Verbose > 0 || !formatter.IsCurrentFormat("text") {
formatter.Print(result)
} else {
formatter.PrintResult("Sync Completed")
}
}

func pushSketch(sketch sketches.Sketch, sketchbook string, bearerToken string) error {
Expand Down
78 changes: 78 additions & 0 deletions cmd/output/sketch_structs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* This file is part of arduino-cli.
*
* arduino-cli is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* As a special exception, you may use this file as part of a free software
* library without restriction. Specifically, if other files instantiate
* templates or use macros or inline functions from this file, or you compile
* this file and link it with other files to produce an executable, this
* file does not by itself cause the resulting executable to be covered by
* the GNU General Public License. This exception does not however
* invalidate any other reasons why the executable file might be covered by
* the GNU General Public License.
*
* Copyright 2017 ARDUINO AG (http://www.arduino.cc/)
*/

package output

import "fmt"

// SketchSyncResult contains the result of an `arduino sketch sync` operation.
type SketchSyncResult struct {
PushedSketches []string `json:"pushedSketches,required"`
PulledSketches []string `json:"pulledSketches,required"`
SkippedSketches []string `json:"skippedSketches,required"`
Errors []SketchSyncError `json:"errors,required"`
}

// SketchSyncError represents an error during a `arduino sketch sync` operation.
type SketchSyncError struct {
Sketch string `json:"sketch,required"`
Error error `json:"error,required"`
}

// String returns a string representation of the object. For this object
// it is used to provide verbose info of a sync process.
func (ssr SketchSyncResult) String() string {
totalSketches := len(ssr.SkippedSketches) + len(ssr.PushedSketches) + len(ssr.PulledSketches) + len(ssr.Errors)
//this function iterates an array and if it's not empty pretty prints it.
iterate := func(array []string, header string) string {
ret := ""
if len(array) > 0 {
ret += fmt.Sprintln(header)
for _, item := range array {
ret += fmt.Sprintln(" -", item)
}
}
return ret
}

ret := fmt.Sprintf("%d sketches synced:\n", totalSketches)

ret += iterate(ssr.PushedSketches, "Pushed Sketches:")
ret += iterate(ssr.PulledSketches, "Pulled Sketches:")
ret += iterate(ssr.SkippedSketches, "Skipped Sketches:")

if len(ssr.Errors) > 0 {
ret += fmt.Sprintln("Errors:")
for _, item := range ssr.Errors {
ret += fmt.Sprintf(" - Sketch %s : %s\n", item.Sketch, item.Error)
}
}

return ret
}

0 comments on commit 0854081

Please sign in to comment.