ch4/ch4-05 #158
Replies: 13 comments 5 replies
-
%-5d 最小宽度5,左对齐 |
Beta Was this translation helpful? Give feedback.
-
4.10github.go
main.go
|
Beta Was this translation helpful? Give feedback.
-
4.12test.go
main.go
|
Beta Was this translation helpful? Give feedback.
-
4.13 package main import ( // get movies from omdbapi.com // get poster url from movies json // download poster from url |
Beta Was this translation helpful? Give feedback.
-
从下往上看👁 package main
import (
"time"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"log"
"os"
"bufio"
"io/ioutil"
"regexp"
"io"
"flag"
"os/exec"
)
const IssuesURL = "https://api.github.com/search/issues"
type IssuesSearchResult struct {
TotalCount int `json:"total_count"`
Items []*Issue
}
type Issue struct {
Number int
HTMLURL string `json:"html_url"`
Title string
State string
User *User
CreatedAt time.Time `json:"created_at"`
Body string
}
type User struct {
Login string
HTMLURL string `json:"html_url"`
}
type PosterResp struct {
Poster string
Title string
}
type XkcdResp struct {
Img string
Title string
Link string
}
func SearchIssues(terms []string) (*IssuesSearchResult, error) {
q := url.QueryEscape(strings.Join(terms, " "))
fmt.Println(IssuesURL + "?q=" + q)
resp, err := http.Get(IssuesURL + "?q=" + q)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("search query failed: %s", resp.Status)
}
var result IssuesSearchResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
resp.Body.Close()
return nil, err
}
resp.Body.Close()
return &result, nil
}
func CategorizedCreatedAt(issues []*Issue) {
now := time.Now().UTC()
var OverYear, LessYear, LessMonth []*Issue
for _, issue := range issues {
issueTime := issue.CreatedAt.UTC()
isOverOneYear := now.Year() - issueTime.Year() > 1
isLessOneMonth := !isOverOneYear && (now.Month() - issueTime.Month() < 1)
if isOverOneYear {
OverYear = append(OverYear, issue)
} else if !isOverOneYear {
LessYear = append(LessYear, issue)
} else if isLessOneMonth {
LessMonth = append(LessMonth, issue)
}
}
for _, issue := range OverYear {
fmt.Printf("Over one Year: #%-5d %9.9s %.55s\n", issue.Number, issue.User.Login, issue.Title)
}
for _, issue := range LessYear {
fmt.Printf("Less one Year: #%-5d %9.9s %.55s\n", issue.Number, issue.User.Login, issue.Title)
}
for _, issue := range LessMonth {
fmt.Printf("Less one Month: #%-5d %9.9s %.55s\n", issue.Number, issue.User.Login, issue.Title)
}
}
func parseBody(response *http.Response) ([]byte, error) {
body, err := ioutil.ReadAll(response.Body)
return body, err
}
func queryUrl(url string) ([]byte, error) {
// 1. query url
resp, err := http.Get(url)
if err != nil || resp.StatusCode != http.StatusOK {
fmt.Printf("query url error: %v", err)
return nil, err
}
// 2. parse response body
body, err := parseBody(resp)
if err != nil {
fmt.Printf("parse response error: %v", err)
return nil, err
}
defer resp.Body.Close()
return body, nil
}
func Poster() {
// 1. get movie name
// you can apply free apiKey
ImageUrl := "http://www.omdbapi.com/?apikey=[your key]&"
fmt.Println("---------------- Input move name ----------------------------")
reader := bufio.NewReader(os.Stdin)
movieName, _, err := reader.ReadLine()
if err != nil {
fmt.Println("cannot get movie name")
return
}
// 2. query movie info
escapeName := url.QueryEscape(string(movieName))
movieUrl := ImageUrl + "&plot=full&t=" + escapeName
movieBody, err := queryUrl(movieUrl)
if err != nil {
return
}
// 3. download image
var jsonMovieBody PosterResp
json.Unmarshal(movieBody, &jsonMovieBody)
imgUrl := jsonMovieBody.Poster
imgBody, err := queryUrl(imgUrl)
if err != nil {
return
}
// 4. write file
var validSuffix = regexp.MustCompile(`\.(jpe?g|web|png|gif)$`)
suffix := validSuffix.FindString(imgUrl)
fileName := string(movieName) + suffix
fileErr := ioutil.WriteFile(fileName, imgBody, 0644)
if fileErr != nil {
log.Fatal(fileErr)
}
}
func Xkcd() {
xkcdUrl := "https://xkcd.com/"
xkcdSuffix := "/info.0.json"
f, err := os.OpenFile("storage.txt", os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
log.Fatal(err)
}
var max int
fmt.Println("------------------- how match you want -----------------------")
fmt.Scanln(&max)
for i := 1; i < max; i++ {
url := xkcdUrl + fmt.Sprint(i) + xkcdSuffix
fmt.Printf("query url: %v\n", url)
body, err := queryUrl(url)
if err != nil {
fmt.Printf("query url error: %v\n", err)
break
}
var parseBody XkcdResp
if err := json.Unmarshal(body, &parseBody); err != nil {
log.Fatalf("JSON unmarshaling failed: %s", err)
continue
}
row := parseBody.Img + parseBody.Title + parseBody.Link + "\n"
if _, err := f.Write([]byte(row)); err != nil {
fmt.Printf("write info error: %v\n", err)
}
}
f.Close()
rf, rErr := os.OpenFile("storage.txt", os.O_RDONLY, 0)
if rErr != nil {
log.Fatal(err)
}
var order int
fmt.Println("------------------- want order -----------------------")
fmt.Scanln(&order)
reader := bufio.NewReader(rf)
flag := 0
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
fmt.Printf("EOF: %#v\n", line)
break
}
}
if flag == order - 1 {
fmt.Printf("%v", line)
}
flag++
}
defer f.Close()
}
func queryIssue(address, method string) {
// 1. generator template
f, err := os.OpenFile("template.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
log.Fatal(err)
return
}
template := "title:\nbody:\nstate:\n"
_, wErr := f.WriteString(template)
if err != nil {
fmt.Printf("generator template error: %v", wErr)
return
}
f.Close()
// 2. open template, waiting for user write
// Attention: use case run on mac os
cmd := exec.Command("open", "template.txt")
if err := cmd.Run(); err != nil {
os.Exit(1)
log.Fatal(err)
return
}
var moveOn string
fmt.Println("-- waiting fo input title, body and state, type continue to move on --")
fmt.Scanln(&moveOn)
for moveOn != "continue" {}
// 3. read template, get title body etc.
f, ferr := os.OpenFile("template.txt", os.O_RDONLY, 0666)
if ferr != nil {
log.Fatal(ferr)
return
}
reader := bufio.NewReader(f)
lines := make(map[string]string)
for {
line, _, err := reader.ReadLine()
if err != nil {
if err == io.EOF {
break
}
return
}
parseLine := string(line)
if strings.Contains(parseLine, ":") {
arr := strings.Split(parseLine, ":")
if arr[1] != "" {
lines[arr[0]] = arr[1]
}
}
}
defer f.Close()
if len(lines) > 0 {
jsonBytes, err := json.MarshalIndent(lines, "", " ")
if err != nil {
log.Fatal(err)
}
client := &http.Client{}
req, err := http.NewRequest(method, address, strings.NewReader(string(jsonBytes)))
req.Header.Add("Content-Type", "application/json")
// https://docs.github.com/en/rest/overview/authenticating-to-the-rest-api?apiVersion=2022-11-28
req.Header.Add("Authorization", "Bearer [your token]")
resp, err := client.Do(req)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Printf("%v \n %v \n %v", address, string(body), strings.NewReader(string(jsonBytes)))
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
if resp.StatusCode == http.StatusOK {
fmt.Printf("Success: %v", string(body))
}
defer resp.Body.Close()
} else {
fmt.Println("nothing input, quit")
}
}
func IssueOperation() {
var owner string
var repo string
var issueNumber string
var operationType int
const OperationURL = "https://api.github.com/repos/"
flag.IntVar(&operationType, "operation", 0, "Specify operation type: 1 -- create; 2 -- get; 3 -- update; 4 -- delete")
flag.StringVar(&owner, "owner", "", "The account owner of the repository. The name is not case sensitive.")
flag.StringVar(&repo, "repo", "", "The name of the repository without the .git extension. The name is not case sensitive.")
flag.StringVar(&issueNumber, "issue_n", "", "The number that identifies the issue.")
flag.Parse()
if owner != "" && repo != "" && operationType != 0 {
var queryAddress string
switch operationType {
case 1:
// command: go run hello.go -operation=1 -owner=[your owner] -repo=[your repo]
queryAddress = OperationURL + owner + "/" + repo + "/issues"
queryIssue(queryAddress, "POST")
case 2:
// command go run hello.go -operation=2 -owner=golang -repo=go -issue_n=5680
queryAddress = OperationURL + owner + "/" + repo + "/issues/" + issueNumber
body, err := queryUrl(queryAddress)
if err != nil {
fmt.Printf("get issue error: %v\n", err)
return
}
var result Issue
parseErr := json.Unmarshal(body, &result)
if parseErr != nil {
fmt.Printf("parse body error: %v", parseErr)
return
}
fmt.Println(result)
case 3:
// command: go run hello.go -operation=3 -owner=[your owner] -repo=[your repo] -issue_n=1
queryAddress = OperationURL + owner + "/" + repo + "/issues/" + issueNumber
queryIssue(queryAddress, "PATCH")
case 4:
// command: go run hello.go -operation=4 -owner=[your owner] -repo=[your repo] -issue_n=1
// set state -> 4
queryAddress = OperationURL + owner + "/" + repo + "/issues/" + issueNumber
queryIssue(queryAddress, "PATCH")
}
} else {
fmt.Println("application need correct input")
}
}
func main() {
result, err := SearchIssues(os.Args[1:])
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d issues:\n", result.TotalCount)
for _, item := range result.Items {
fmt.Printf("#%-5d %9.9s %.55s\n",
item.Number, item.User.Login, item.Title)
}
// Remember to comment other code
fmt.Println("---------------- 4.10 Categorized by time start -----------------")
CategorizedCreatedAt(result.Items)
fmt.Println("---------------- 4.10 Categorized by time end -------------------")
// Remember to comment other code
fmt.Println("---------------- 4.11 Issue start -----------------")
IssueOperation()
fmt.Println("---------------- 4.11 Issue end -------------------")
// Remember to comment other code
fmt.Println("---------------- 4.12 xkcd start -----------------")
Xkcd()
fmt.Println("---------------- 4.12 xkcd end -------------------")
// Remember to comment other code
fmt.Println("---------------- 4.13 Poster start -----------------")
Poster()
fmt.Println("---------------- 4.13 Poster end -------------------")
} |
Beta Was this translation helpful? Give feedback.
-
4.13func Poster(filmName string) (*Film, error) {
// 安全地编码电影名称
safeFilmName := url.QueryEscape(filmName)
requestURL := fmt.Sprintf("%s?apikey=9f693ec9&t=%s", BaseUrl, safeFilmName)
resp, err := http.Get(requestURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("search query failed: %s", resp.Status)
}
var film Film
if err := json.NewDecoder(resp.Body).Decode(&film); err != nil {
return nil, err
}
if film.Poster == "" {
return &film, fmt.Errorf("no poster URL found")
}
// 获取海报图像
resp, err = http.Get(film.Poster)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get poster image")
}
img, err := jpeg.Decode(resp.Body)
if err != nil {
return nil, err
}
filePath := "./pic/" + film.Title + ".jpg"
file, err := os.Create(filePath)
if err != nil {
return nil, err
}
defer file.Close()
if err = jpeg.Encode(file, img, &jpeg.Options{Quality: 75}); err != nil {
return nil, err
}
fmt.Println("Poster saved to", filePath)
return &film, nil
} |
Beta Was this translation helpful? Give feedback.
-
// Issues prints a table of GitHub issues matching the search terms. import (
) func main() {
} |
Beta Was this translation helpful? Give feedback.
-
// Issues prints a table of GitHub issues matching the search terms. import ( // Issue 定义了我们要提交给 GitHub API 的 issue 数据结构 const baseURL = "https://api.github.com" // getAuthToken 从环境变量中获取 GitHub 访问令牌 // getEditorInput 会新建一个临时文件,将 initial 内容写入后调用默认编辑器,编辑结束后返回文件内容
} // createIssue 通过 GitHub API 创建 issue
} // readIssue 读取指定 issue 信息
} // updateIssue 更新指定 issue 的标题和正文
} // closeIssue 将指定 issue 状态设置为 closed func main() {
} |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
package poster import ( // Movie 用于解析 OMDB 返回的 JSON 数据 const apiKey = "744303" // GetPoster 根据电影名称和 API key 获取电影信息,并下载海报
} import (
) const apiKey = "768403" func main() {
} |
Beta Was this translation helpful? Give feedback.
-
4.12package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strings"
)
type Comic struct {
Num int `json:"num"`
Title string `json:"safe_title"`
Alt string `json:"alt"`
Transcript string `json:"transcript"`
Img string `json:"img"`
}
const dir = "xkcd_data"
func main() {
// comic, err := fetchComic(571)
// if err != nil {
// log.Fatal(err)
// }
// fmt.Printf("Title: %s\nAlt: %s\n", comic.Title, comic.Alt)
// 示例:下载前 20 条测试用
err := downloadAllComics(1, 20)
if err != nil {
log.Fatal(err)
}
if len(os.Args) < 2 {
log.Fatal("请提供一个关键词作为搜索参数")
}
keyword := os.Args[1]
comics, err := loadComics()
if err != nil {
log.Fatal(err)
}
matches := searchComics(comics, keyword)
for _, comic := range matches {
fmt.Printf("https://xkcd.com/%d/ - %s\n", comic.Num, comic.Title)
}
}
func fetchComic(num int) (*Comic, error) {
url := fmt.Sprintf("https://xkcd.com/%d/info.0.json", num)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
var comic Comic
if err := json.NewDecoder(resp.Body).Decode(&comic); err != nil {
return nil, err
}
return &comic, err
}
func downloadAllComics(start, end int) error {
os.MkdirAll(dir, os.ModePerm)
for i := start; i <= end; i++ {
num := i
filename := fmt.Sprintf("%s/%d.json", dir, num)
if _, err := os.Stat(filename); err == nil {
// 文件已存在
continue
}
comic, err := fetchComic(i)
if err != nil {
log.Printf("Failed to fetch comic %d: %v", num, err)
continue // 忽略错误继续下一个
}
file, err := os.Create(filename)
if err != nil {
log.Printf("Failed to create file for comic %d: %v", num, err)
continue
}
if err := json.NewEncoder(file).Encode(comic); err != nil {
log.Printf("Failed to encode comic %d: %v", num, err)
}
file.Close()
}
return nil
}
func loadComics() ([]Comic, error) {
entries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
var comics []Comic
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".json") {
continue
}
file, err := os.Open(filepath.Join(dir, entry.Name()))
if err != nil {
log.Printf("error: %v", err)
continue
}
var comic Comic
err = json.NewDecoder(file).Decode(&comic)
if err != nil {
log.Printf("error: %v", err)
}
comics = append(comics, comic)
file.Close()
}
return comics, nil
}
func searchComics(comics []Comic, keyword string) []Comic {
lowerKeyword := strings.ToLower(keyword)
var results []Comic
for _, comic := range comics {
content := comic.Title + " " + comic.Alt + " " + comic.Transcript
content = strings.ToLower(content)
if strings.Contains(content, lowerKeyword) {
results = append(results, comic)
}
}
return results
} |
Beta Was this translation helpful? Give feedback.
-
exercise 4.10 func makeType(createAt time.Time) IssueType {
if isOneYear(createAt) {
return OneYear
}
if isOneMonth(createAt) {
return OneMonth
}
return Others
}
func isOneMonth(createAt time.Time) bool {
now := time.Now()
oneMonthLater := createAt.AddDate(0, 1, 0)
return now.Before(oneMonthLater) || now.Equal(oneMonthLater)
}
func isOneYear(createAt time.Time) bool {
now := time.Now()
oneYearLater := createAt.AddDate(1, 0, 0)
return now.Before(oneYearLater) || now.Equal(oneYearLater)
}
func isOthers(createAt time.Time) bool {
return !isOneYear(createAt) && !isOneMonth(createAt)
}
// 练习 4.10: 修改issues程序,根据问题的时间进行分类,比如不到一个月的、不到一年的、超过一年。
func main() {
result, err := github.SearchIssues(os.Args[1:])
if err != nil {
log.Fatal(err)
}
var m map[IssueType][]*github.Issue
// 分类
for _, item := range result.Items {
t := makeType(item.CreatedAt)
m[t] = append(m[t], item)
}
fmt.Printf("%d issues:\n", result.TotalCount)
for t, times := range m {
println("issue type ", t)
for _, item := range times {
fmt.Printf("#%-5d %9.9s %.55s\n",
item.Number, item.User.Login, item.Title)
}
}
} |
Beta Was this translation helpful? Give feedback.
-
exercise 4.12 type XKCDComic struct {
Month string `json:"month"` // 月份(字符串形式,如 "4")
Num int `json:"num"` // 漫画编号(如 571)
Link string `json:"link"` // 链接(示例中为空字符串)
Year string `json:"year"` // 年份(字符串形式,如 "2009")
News string `json:"news"` // 新闻信息(示例中为空字符串)
SafeTitle string `json:"safe_title"` // 安全标题(不含特殊字符,如 "Can't Sleep")
Transcript string `json:"transcript"` // 漫画文本脚本(含多行和特殊符号)
Alt string `json:"alt"` // 悬停提示文本(如关于 electric sheep 的提示)
Img string `json:"img"` // 图片 URL(如 "https://imgs.xkcd.com/comics/cant_sleep.png")
Title string `json:"title"` // 完整标题(可能与 safe_title 相同)
Day string `json:"day"` // 发布日期中的日(字符串形式,如 "20")
URL string `json:"url"` // 漫画的链接
}
const baseURL = "https://xkcd.com/"
// cache comic details
var details []*XKCDComic
func main() {
start := flag.Int("start", 1, "search start index")
end := flag.Int("end", 10, "search end index")
search := flag.String("search", "", "search content")
flag.Parse()
// validate
if *start > *end {
fmt.Println("search start index is greater than end")
}
if *start <= 0 || *start > 1000 {
fmt.Println("start must be between 0 and 1000")
}
if *end <= 0 || *end > 1000 {
fmt.Println("end must be between 0 and 1000")
}
if *search == "" {
fmt.Println("search is required")
}
buildLocalCache(*start, *end)
searchComic(*search)
}
func buildLocalCache(start, end int) {
var (
wg sync.WaitGroup
results = make(chan *XKCDComic, end-start+1)
)
for i := start; i <= end; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Printf("request %d comic\n", i)
resp, err := http.Get(fmt.Sprintf("%s%d/info.0.json", baseURL, i))
if err != nil {
log.Printf("failed to fetch comic %d: %v", i, err)
return
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("failed to read comic %d: %v", i, err)
return
}
var x XKCDComic
if err := json.Unmarshal(b, &x); err != nil {
log.Printf("failed to parse comic %d: %v", i, err)
return
}
x.URL = fmt.Sprintf("%s%d", baseURL, i)
results <- &x
}(i)
}
wg.Wait()
close(results)
for x := range results {
details = append(details, x)
}
fmt.Printf("%d comics found\n", len(details))
}
func buildLocalCache2(start, end int) {
var (
wg sync.WaitGroup
mu sync.Mutex
results = make(chan *XKCDComic, end-start)
)
// 启动 goroutine 消费 results
go func() {
wg.Wait()
close(results)
}()
for i := start; i <= end; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
resp, err := http.Get(fmt.Sprintf("%s%d/info.0.json", baseURL, id))
if err != nil {
log.Printf("failed to fetch comic %d: %v", id, err)
return
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("failed to read comic %d: %v", id, err)
return
}
var x XKCDComic
if err := json.Unmarshal(b, &x); err != nil {
log.Printf("failed to parse comic %d: %v", id, err)
return
}
x.URL = fmt.Sprintf("%s%d", baseURL, id)
results <- &x
}(i)
}
// 安全地收集结果
for x := range results {
mu.Lock()
details = append(details, x)
mu.Unlock()
}
fmt.Printf("%d comics found\n", len(details))
}
func searchComic(search string) {
var found *XKCDComic
for _, x := range details {
if strings.Contains(strings.ToLower(x.Title), strings.ToLower(search)) {
found = x
break
}
if strings.Contains(strings.ToLower(x.Transcript), strings.ToLower(search)) {
found = x
break
}
if strings.Contains(strings.ToLower(x.SafeTitle), strings.ToLower(search)) {
found = x
break
}
}
if found == nil {
fmt.Println("no comic found")
return
}
fmt.Printf("comic found url: %s\n", found.URL)
} |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
ch4/ch4-05
中文版
https://gopl-zh.github.io/ch4/ch4-05.html
Beta Was this translation helpful? Give feedback.
All reactions