goso

package module
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 7, 2025 License: MIT Imports: 17 Imported by: 0

README

goso - Stack Overflow CLI Tool written in Go

GitHub Actions Workflow Status Go Reference GitHub go.mod Go version Go Report Card GitHub Release GitHub Downloads (all assets, all releases)

Features

  • Syntax highlighting
  • Fast search
  • Easy to use
  • Support for Linux, Windows, macOS

Demo

goso - Animated gif demo

Installation

You can download the binary for your platform from Releases page.

Alternatively, you can install it using go install command (requires Go 1.23 or later):

CGO_ENABLED=0 go install -ldflags "-s -w" -trimpath github.com/shadowy-pycoder/goso/cmd/goso@latest

This will install the goso binary to your $GOPATH/bin directory.

If none of the above works for you, you can use the Makefile to build the binary from source.

git clone https://github.com/shadowy-pycoder/goso.git
cd goso
make build

Search Engine Setup

Google Search JSON API

This approach employs Custom Search JSON API from Google to obtain most relevant results from Stack Overflow. So, to make it work, you need to get an API key from Google and also a Search Engine ID. That gives you 100 requests per day, which I believe is enough for most use cases.

Setup your Search Engine ID like this:

Screenshot from 2024-11-12 13-17-26

Add variables to your environment:

echo "export GOSO_API_KEY=<YOUR_API_KEY>" >> $HOME/.profile
echo "export GOSO_SE=<YOUR_SEARCH_ENGINE_ID>" >> $HOME/.profile
source $HOME/.profile
OpenSerp API

goso also supports OpenSERP (Search Engine Results Page) from Karust. This is a completely FREE alternative to the Google Search JSON API, though it works a little bit slower, but gives basically the same results.

So, to make it work, you need to run OperSERP server locally. You can do it like this:

With Docker:

docker run -p 127.0.0.1:7000:7000 -it karust/openserp serve -a 0.0.0.0 -p 7000

Or as a CLI command:

openserp serve 

You can learn more on how to install OpenSERP here.

Once you have it running, add variables to your environment:

echo "export GOSO_OS_HOST=127.0.0.1" >> $HOME/.profile
echo "export GOSO_OS_PORT=7000" >> $HOME/.profile
source $HOME/.profile

These variables will have priority over the GOSO_API_KEY and GOSO_SE.

Usage

goso -h
                                                                  
 .d88b.   .d88b.  .d8888b   .d88b.  
d88P"88b d88""88b 88K      d88""88b 
888  888 888  888 "Y8888b. 888  888 
Y88b 888 Y88..88P      X88 Y88..88P 
 "Y88888  "Y88P"   88888P'  "Y88P"  
     888                            
Y8b d88P  Stack Overlow CLI Tool by shadowy-pycoder                         
 "Y88P"   GitHub: https://github.com/shadowy-pycoder/goso                        
                                                                                                                                                                                              
Usage: goso [OPTIONS] QUERY
Options:
  -h    Show this help message and exit.
  -a int
        The number of answers for each result [min=1, max=10] (default 3)
  -l string
        The name of Chroma lexer. See https://github.com/alecthomas/chroma/tree/master/lexers/embedded (default "bash")
  -q int
        The number of questions [min=1, max=10] (default 10)
  -s string
        The name of Chroma style. See https://xyproto.github.io/splash/docs/ (default "onedark")
  -v    print version

It is possible to adjust default values for the number of questions and answers, lexer and style.

echo "export GOSO_LEXER=python" >> $HOME/.profile
echo "export GOSO_STYLE=onedark" >> $HOME/.profile
echo "export GOSO_ANSWERS=5" >> $HOME/.profile
echo "export GOSO_QUESTIONS=5" >> $HOME/.profile
source $HOME/.profile

By default, goso will not show question body, but you can enable it like this:

echo "export GOSO_SHOW_QUESTIONS=1" >> $HOME/.profile

[!WARNING] Enabling the question body requires additional call to Stack Overflow API.

Example

goso  -l go -s onedark -q 1 -a 1 Sort maps in Golang

Output:

Screenshot from 2024-11-14 10-16-52

You can also use less command for instance to page through the results:

#!/bin/bash
goso "$@" | less -F -R -X

Contributing

Are you a developer?

  • Fork the repository
  • Create your feature branch: git switch -c my-new-feature
  • Commit your changes: git commit -am 'Add some feature'
  • Push to the branch: git push origin my-new-feature
  • Submit a pull request

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Version string = "v0.0.4"

Functions

func FetchGoogle

func FetchGoogle(conf *Config, results map[int]*Result) error

func FetchOpenSerp added in v0.0.2

func FetchOpenSerp(conf *Config, results map[int]*Result) error

func FetchStackOverflow

func FetchStackOverflow(conf *Config, results map[int]*Result) error

func GetAnswers

func GetAnswers(conf *Config,
	fetchResults func(*Config, map[int]*Result) error,
	fetchAnswers func(*Config, map[int]*Result) error,
) (string, error)

Types

type Answer

type Answer struct {
	Title      string
	Author     string
	Score      int
	Body       string
	Link       string
	IsAccepted bool
	Date       time.Time
}

func (*Answer) String

func (a *Answer) String() string

type Config

type Config struct {
	ApiKey       string
	SearchEngine string
	Query        string
	Style        string
	Lexer        string
	QuestionNum  int
	ShowQuestion bool
	AnswerNum    int
	OpenSerpHost string
	OpenSerpPort int
	Client       *http.Client
}

type GoogleSearchResult

type GoogleSearchResult struct {
	Kind string `json:"kind"`
	URL  struct {
		Type     string `json:"type"`
		Template string `json:"template"`
	} `json:"url"`
	Queries struct {
		Request []struct {
			Title          string `json:"title"`
			TotalResults   string `json:"totalResults"`
			SearchTerms    string `json:"searchTerms"`
			Count          int    `json:"count"`
			StartIndex     int    `json:"startIndex"`
			InputEncoding  string `json:"inputEncoding"`
			OutputEncoding string `json:"outputEncoding"`
			Safe           string `json:"safe"`
			Cx             string `json:"cx"`
		} `json:"request"`
		NextPage []struct {
			Title          string `json:"title"`
			TotalResults   string `json:"totalResults"`
			SearchTerms    string `json:"searchTerms"`
			Count          int    `json:"count"`
			StartIndex     int    `json:"startIndex"`
			InputEncoding  string `json:"inputEncoding"`
			OutputEncoding string `json:"outputEncoding"`
			Safe           string `json:"safe"`
			Cx             string `json:"cx"`
		} `json:"nextPage"`
	} `json:"queries"`
	Context struct {
		Title string `json:"title"`
	} `json:"context"`
	SearchInformation struct {
		SearchTime            float64 `json:"searchTime"`
		FormattedSearchTime   string  `json:"formattedSearchTime"`
		TotalResults          string  `json:"totalResults"`
		FormattedTotalResults string  `json:"formattedTotalResults"`
	} `json:"searchInformation"`
	Items []struct {
		Kind             string `json:"kind"`
		Title            string `json:"title"`
		HTMLTitle        string `json:"htmlTitle"`
		Link             string `json:"link"`
		DisplayLink      string `json:"displayLink"`
		Snippet          string `json:"snippet"`
		HTMLSnippet      string `json:"htmlSnippet"`
		FormattedURL     string `json:"formattedUrl"`
		HTMLFormattedURL string `json:"htmlFormattedUrl"`
		Pagemap          struct {
			CseThumbnail []struct {
				Src    string `json:"src"`
				Width  string `json:"width"`
				Height string `json:"height"`
			} `json:"cse_thumbnail"`
			Qapage []struct {
				Image              string `json:"image"`
				Primaryimageofpage string `json:"primaryimageofpage"`
				Name               string `json:"name"`
				Description        string `json:"description"`
			} `json:"qapage"`
			Question []struct {
				Image       string `json:"image"`
				Upvotecount string `json:"upvotecount"`
				Answercount string `json:"answercount"`
				Name        string `json:"name"`
				Datecreated string `json:"datecreated"`
				Text        string `json:"text"`
				URL         string `json:"url"`
			} `json:"question"`
			Answer []struct {
				Upvotecount  string `json:"upvotecount"`
				Commentcount string `json:"commentcount,omitempty"`
				Text         string `json:"text"`
				Datecreated  string `json:"datecreated"`
				URL          string `json:"url"`
			} `json:"answer"`
			Person []struct {
				Name string `json:"name"`
			} `json:"person"`
			Metatags []struct {
				OgImage            string `json:"og:image"`
				OgType             string `json:"og:type"`
				TwitterCard        string `json:"twitter:card"`
				TwitterTitle       string `json:"twitter:title"`
				OgSiteName         string `json:"og:site_name"`
				TwitterDomain      string `json:"twitter:domain"`
				Viewport           string `json:"viewport"`
				TwitterDescription string `json:"twitter:description"`
				Bingbot            string `json:"bingbot"`
				OgURL              string `json:"og:url"`
			} `json:"metatags"`
			CseImage []struct {
				Src string `json:"src"`
			} `json:"cse_image"`
		} `json:"pagemap"`
	} `json:"items"`
}

type OpenSerpResult added in v0.0.2

type OpenSerpResult struct {
	Rank        int    `json:"rank"`
	URL         string `json:"url"`
	Title       string `json:"title"`
	Description string `json:"description"`
	Ad          bool   `json:"ad"`
}

type Result

type Result struct {
	Title       string
	Link        string
	QuestionId  int
	UpvoteCount int
	Date        time.Time
	Body        string
	Answers     []*Answer
}

func (*Result) String

func (r *Result) String() string

type StackOverflowQuestion added in v0.0.4

type StackOverflowQuestion struct {
	Items []struct {
		Tags  []string `json:"tags"`
		Owner struct {
			AccountID    int    `json:"account_id"`
			Reputation   int    `json:"reputation"`
			UserID       int    `json:"user_id"`
			UserType     string `json:"user_type"`
			ProfileImage string `json:"profile_image"`
			DisplayName  string `json:"display_name"`
			Link         string `json:"link"`
		} `json:"owner"`
		IsAnswered       bool   `json:"is_answered"`
		ViewCount        int    `json:"view_count"`
		ProtectedDate    int    `json:"protected_date"`
		AnswerCount      int    `json:"answer_count"`
		Score            int    `json:"score"`
		LastActivityDate int    `json:"last_activity_date"`
		CreationDate     int    `json:"creation_date"`
		LastEditDate     int    `json:"last_edit_date,omitempty"`
		QuestionID       int    `json:"question_id"`
		ContentLicense   string `json:"content_license"`
		Link             string `json:"link"`
		Title            string `json:"title"`
		Body             string `json:"body"`
	} `json:"items"`
	HasMore        bool `json:"has_more"`
	QuotaMax       int  `json:"quota_max"`
	QuotaRemaining int  `json:"quota_remaining"`
}

type StackOverflowResult

type StackOverflowResult struct {
	Items []struct {
		Owner struct {
			AccountID    int    `json:"account_id"`
			Reputation   int    `json:"reputation"`
			UserID       int    `json:"user_id"`
			UserType     string `json:"user_type"`
			AcceptRate   int    `json:"accept_rate"`
			ProfileImage string `json:"profile_image"`
			DisplayName  string `json:"display_name"`
			Link         string `json:"link"`
		} `json:"owner"`
		IsAccepted         bool   `json:"is_accepted"`
		Score              int    `json:"score"`
		LastActivityDate   int    `json:"last_activity_date"`
		LastEditDate       int    `json:"last_edit_date,omitempty"`
		CreationDate       int    `json:"creation_date"`
		AnswerID           int    `json:"answer_id"`
		QuestionID         int    `json:"question_id"`
		ContentLicense     string `json:"content_license"`
		Body               string `json:"body"`
		CommunityOwnedDate int    `json:"community_owned_date,omitempty"`
	} `json:"items"`
	HasMore        bool `json:"has_more"`
	QuotaMax       int  `json:"quota_max"`
	QuotaRemaining int  `json:"quota_remaining"`
}

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL