go-show-notes-generator/

Я потихоньку слежу за развитием языка Go и в последнее время он начинает нравится мне все больше и больше. Недавно дошли руки написать небольшую программку, генерирующую шоуноты к подкасту по списку ссылок. Ну и, поскольку это все-таки Go, программка была сделана многопоточной.

Напоминаю, что ранее в этом блоге публиковались аналогичные программки на Kotlin и Rust . Так что, не буду в очередной раз расписывать, что, как и зачем, а просто приведу код:

package main

import (
«fmt»
utf8string «golang.org/x/exp/utf8string»
«io/ioutil»
«net/http»
«os»
«regexp»
«runtime»
«strings»
)

type task struct {
num int
url string
}

type result struct {
num int
url string
title string
}

func formatTitle ( title string ) string {
title = strings . TrimSpace ( title )
utf8title := utf8string . NewString ( title )

// or: utf8.RuneCountInString(title)
if utf8title . RuneCount () > 64 {
return fmt . Sprint ( utf8title . Slice ( 0 , 64 ), «…» )
} else {
return title
}
}

func getTitle ( url string ) string {
req , reqErr := http . NewRequest ( «GET» , url , nil )
if reqErr != nil {
return fmt . Sprint ( «reqErr: » , reqErr . Error ())
}
req . Header . Set ( «User-Agent» , «Mozilla/5.0 (X11; Linux x86_64) » +
«Apple WebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.81 » +
«Safari/537.36» )

// resp, getErr := http.Get(url)

client := http . Client {}
resp , getErr := client . Do ( req )
if getErr != nil {
return fmt . Sprint ( «getErr: » , getErr . Error ())
}
defer resp . Body . Close ()

bytes , err := ioutil . ReadAll ( resp . Body )
if err != nil {
return err . Error ()
}

content := string ( bytes )
r , _ := regexp . Compile ( «(?is)<title[^>]*>(.*?)</title>» )
matches := r . FindStringSubmatch ( content )
if len ( matches ) > 1 {
return formatTitle ( matches [ 1 ])
} else {
return «[NO TITLE]»
}
}

func worker ( workerId int , tasksChan < chan task ,
resultsChan chan< result ) {
for {
tsk := < tasksChan
fmt . Printf ( «Worker %d — processing %s… n » , workerId , tsk . url )

rslt := result {
num : tsk . num ,
title : getTitle ( tsk . url ),
url : tsk . url ,
}
resultsChan < rslt
}
}

func main () {
if len ( os . Args ) < 2 {
fmt . Fprintf ( os . Stderr , «Usage: %s <infile> n » , os . Args [ 0 ])
os . Exit ( 1 )
}

bytes , ferr := ioutil . ReadFile ( os . Args [ 1 ])
if ferr != nil {
panic ( ferr )
}

fileContent := string ( bytes )

r , _ := regexp . Compile ( «(?i)https?://[^ \ s]+» )
urls := r . FindAllString ( fileContent , 1000 )
urlsNumber := len ( urls )

tasksChan := make ( chan task , urlsNumber )
resultsChan := make ( chan result , urlsNumber )

cpuNum := runtime . NumCPU ()
for i := 0 ; i < cpuNum ; i ++ {
go worker ( i , tasksChan , resultsChan )
}

for idx , url := range urls {
tsk := task { num : idx , url : url }
tasksChan < tsk
}

results := make ([] result , urlsNumber )
for i := 0 ; i < urlsNumber ; i ++ {
res := < resultsChan
results [ res . num ] = res
}

fmt . Print ( «<ul> n » )
for i := 0 ; i < urlsNumber ; i ++ {
fmt . Printf ( «<li><a href= » %s «» >%s</a></li> n «»
results [ i ] . url results [ i ] . title )
}
fmt . Print ( «»</ul> n «» )
}

Пара моментовна которые следует обратить внимание:

  • Для правильного определения длины юникодной строки и взятия подстроки следует использовать пакет exp/utf8string ;
  • Размер буфера у каналов tasksChan и resultsChan устанавливается в urlsNumber. Если этого не сделать
EnglishRussianUkrainian