Skip to content

Commit

Permalink
add projects languages command
Browse files Browse the repository at this point in the history
  • Loading branch information
marriva committed Jan 11, 2024
1 parent 63d7ddd commit 6f1a06f
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 1 deletion.
184 changes: 184 additions & 0 deletions cmd/projects/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package projects
import (
"fmt"
"os"
"strings"
"text/tabwriter"

"github.com/flant/glaball/pkg/client"
Expand Down Expand Up @@ -48,6 +49,31 @@ similarity (introduced in GitLab 14.1) is only available when searching and is l
return cmd
}

func NewLanguagesCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "languages",
Short: "List projects with languages.",
RunE: func(cmd *cobra.Command, args []string) error {
return ListWithLanguages()
},
}

cmd.Flags().Var(util.NewEnumValue(&groupBy, "name", "path"), "group_by",
"Return projects grouped by id, name, path, fields.")

cmd.Flags().Var(util.NewEnumValue(&sortBy, "asc", "desc"), "sort",
"Return projects sorted in asc or desc order. Default is desc")

cmd.Flags().StringSliceVar(&orderBy, "order_by", []string{"count", projectWithLanguagesDefaultField},
`Return projects ordered by id, name, path, created_at, updated_at, last_activity_at, or similarity fields.
repository_size, storage_size, packages_size or wiki_size fields are only allowed for administrators.
similarity (introduced in GitLab 14.1) is only available when searching and is limited to projects that the current user is a member of.`)

listProjectsOptionsFlags(cmd, &listProjectsOptions)

return cmd
}

func listProjectsOptionsFlags(cmd *cobra.Command, opt *gitlab.ListProjectsOptions) {
cmd.Flags().Var(util.NewBoolPtrValue(&opt.Archived), "archived",
"Limit by archived status. (--archived or --no-archived). Default nil")
Expand Down Expand Up @@ -165,6 +191,123 @@ func List() error {
return nil
}

func ListWithLanguages() error {
structT := new(ProjectWithLanguages)
if !sort.ValidOrderBy(orderBy, structT) {
orderBy = append(orderBy, projectWithLanguagesDefaultField)
}

wg := common.Limiter
data := make(chan interface{})

for _, h := range common.Client.Hosts {
fmt.Printf("Fetching projects from %s ...\n", h.URL)
wg.Add(1)
go listProjects(h, listProjectsOptions, wg, data, common.Client.WithCache())
}

go func() {
wg.Wait()
close(data)
}()

projectList := make(sort.Elements, 0)
for e := range data {
projectList = append(projectList, e)
}

if len(projectList) == 0 {
return fmt.Errorf("no projects found")
}

projectsWithLanguages := make(chan interface{})
for _, v := range projectList.Typed() {
wg.Add(1)
go getProjectLanguages(v.Host, v.Struct.(*gitlab.Project), wg, projectsWithLanguages, common.Client.WithCache())
}

go func() {
wg.Wait()
close(projectsWithLanguages)
}()

results, err := sort.FromChannel(projectsWithLanguages, &sort.Options{
OrderBy: orderBy,
SortBy: sortBy,
GroupBy: groupBy,
StructType: structT,
})
if err != nil {
return err
}

projectsWithLanguagesFormat := util.Dict{
{
Key: "COUNT",
Value: "[%d]",
},
{
Key: "REPOSITORY",
Value: "%s",
},
{
Key: "LANGUAGES",
Value: "[%s]",
},
{
Key: "HOST",
Value: "[%s]",
},
{
Key: "CACHED",
Value: "[%s]",
},
}

w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent)
if _, err := fmt.Fprintln(w, strings.Join(projectsWithLanguagesFormat.Keys(), "\t")); err != nil {
return err
}
unique := 0
total := 0

for _, r := range results {
unique++ // todo
total += r.Count //todo

for _, v := range r.Elements.Typed() {
p, ok := v.Struct.(*ProjectWithLanguages)
if !ok {
return fmt.Errorf("unexpected data type: %#v", v.Struct)
}

if err := projectsWithLanguagesFormat.Print(w, "\t",
r.Count,
r.Key,
p.LanguagesNames(),
v.Host.ProjectName(),
r.Cached,
); err != nil {
return err
}
}
}

if err := totalFormat.Print(w, "\n", unique, total, len(wg.Errors())); err != nil {
return err
}

if err := w.Flush(); err != nil {
return err
}

for _, err := range wg.Errors() {
hclog.L().Error(err.Err.Error())
}

return nil
}

func listProjects(h *client.Host, opt gitlab.ListProjectsOptions, wg *limiter.Limiter, data chan<- interface{},
options ...gitlab.RequestOptionFunc) error {

Expand Down Expand Up @@ -193,3 +336,44 @@ func listProjects(h *client.Host, opt gitlab.ListProjectsOptions, wg *limiter.Li

return nil
}

func getProjectLanguages(h *client.Host, project *gitlab.Project, wg *limiter.Limiter,
data chan<- interface{}, options ...gitlab.RequestOptionFunc) error {

defer wg.Done()

wg.Lock()
list, resp, err := h.Client.Projects.GetProjectLanguages(project.ID, options...)
wg.Unlock()
if err != nil {
wg.Error(h, err)
return err
}

data <- sort.Element{
Host: h,
Struct: &ProjectWithLanguages{
Project: project,
Languages: list},
Cached: resp.Header.Get("X-From-Cache") == "1"}

return nil
}

type ProjectWithLanguages struct {
Project *gitlab.Project `json:"project,omitempty"`
Languages *gitlab.ProjectLanguages `json:"languages,omitempty"`
}

func (p ProjectWithLanguages) LanguagesNames() string {
if p.Languages == nil || len(*p.Languages) == 0 {
return "-"
}

keys := make([]string, 0, len(*p.Languages))
for k := range *p.Languages {
keys = append(keys, k)
}

return strings.Join(keys, ", ")
}
4 changes: 3 additions & 1 deletion cmd/projects/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import (
)

const (
projectDefaultField = "web_url"
projectDefaultField = "web_url"
projectWithLanguagesDefaultField = "project.web_url"
)

func NewCmd() *cobra.Command {
Expand All @@ -21,6 +22,7 @@ func NewCmd() *cobra.Command {
NewMergeRequestsCmd(),
NewBranchesCmd(),
NewRegistryCmd(),
NewLanguagesCmd(),
)

return cmd
Expand Down

0 comments on commit 6f1a06f

Please sign in to comment.