30 банковских дней-2
Жена попросила во вчерашнюю программу ряд улучшений внести и пожаловалась, что она на одном из компьютеров на работе запускается медленно. Ну, я решил переписать её на Гоу, раз такое дело, может быстрее стартовать будет. Кроме того, мне всегда интересно посмотреть как одна и та же программа выглядит на разных языках программирования.
package main
import (
"encoding/json"
"os"
s "strconv"
t "time"
"net/http"
"io/ioutil"
"fmt"
r "regexp"
"bufio"
"strings"
fp "path/filepath"
)
type date struct {
D byte
M byte
Y int
}
func CreateDate(d, m string, y int) date {
day, _ := s.Atoi(d)
month, _ := s.Atoi(m)
return date{D:byte(day), M:byte(month), Y:y}
}
func (d date) FindIn(list []date) bool {
for _, cur := range list {
if d.D == cur.D && d.M == cur.M && (d.Y == cur.Y || cur.Y == 0) {
return true
}
}
return false
}
func (d date) ToTime() t.Time {
return t.Date(d.Y, t.Month(d.M), int(d.D), 0, 0, 0, 0, t.UTC)
}
func (d date) IsWeekEnd() bool {
dw := string(d.ToTime().Weekday())
return dw == "Saturday" || dw == "Sunday"
}
func (d date) Add(delta string) date {
dur, _ := t.ParseDuration(delta)
t := d.ToTime().Add(dur)
return date{D:byte(t.Day()), M:byte(t.Month()), Y:t.Year()}
}
func (d date) String() string {
return fmt.Sprintf("%02d.%02d.%04d", d.D, d.M, d.Y)
}
func loadCache(name string) (holidays []date) {
f, err := os.OpenFile(name, os.O_RDONLY, 0666)
if err != nil {
return nil
}
defer f.Close()
if err := json.NewDecoder(f).Decode(&holidays); err != nil {
return nil
}
return
}
func saveCache(name string, holidays []date) bool {
f, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
if err != nil {
return false
}
defer f.Close()
if err := json.NewEncoder(f).Encode(&holidays); err != nil {
return false
}
return true
}
func getCurrentCalend() []byte {
if resp, err := http.Get("http://www.calend.ru/work/"); err == nil {
defer resp.Body.Close()
if body, err := ioutil.ReadAll(resp.Body); err == nil {
return body
}
}
return nil
}
func getCurrentHolidays() (dates []date) {
table, _ := r.Compile(`(?s)<table\s.+?time_of_death(.+)</table>`)
chunk, _ := r.Compile(`(?s)<td\s+class="\S+\s+col5"\s+day="(\d+)"\s+month="(\d+)"`)
dates = []date{}
year := t.Now().Year()
for _, value := range chunk.FindAllStringSubmatch(string(table.Find(getCurrentCalend())), -1) {
dates = append(dates, CreateDate(value[1], value[2], year))
}
return
}
func ReadUserInput(message string) string {
fmt.Print(message)
input, _ := bufio.NewReader(os.Stdin).ReadString('\n')
return input
}
func main() {
var currentholidays []date
year := t.Now().Year()
cachename := s.Itoa(year) + ".json"
// если кеш существует
if _, e := os.Stat(cachename); e == nil {
currentholidays = loadCache(cachename)
} else {
currentholidays = getCurrentHolidays()
saveCache(cachename, currentholidays)
}
// загружаем другие кеши
caches, _ := fp.Glob("*.json")
for _, name := range caches {
if name != cachename {
currentholidays = append(currentholidays, loadCache(name)...)
}
}
// опрашиваем пользователя
mask, _ := r.Compile(`^(?m)(\d+)\D+(\d+)`)
for {
var userdate date
for {
d := mask.FindStringSubmatch(ReadUserInput("Enter date (dd.mm): "))
if len(d) > 0 {
userdate = CreateDate(d[1], d[2], year)
break
}
}
var period int
for {
period, _ = s.Atoi(strings.TrimRight(ReadUserInput("Enter period: "), "\r\n"))
if period > 0 {
break
}
}
for ; period > 0; period-- {
for {
userdate = userdate.Add("24h")
if !userdate.FindIn(currentholidays) && !userdate.IsWeekEnd() {
break
}
}
}
fmt.Printf("Result: %s\n\n", userdate)
}
}
Меня поразил размер скомпилированной программы. Вот эта мелочь под «Маком» занимает 3,9Мб, под «Виндой» — 3,7! Жена попросила убрать гуишное окошко, поэтому я сделал программу полностью консольной. Два разочарования: в регулярных выражениях под Виндой неверно определяется конец строки (не учитывается «\r», судя по всему) и второе — в Гоу нет ничего, чтобы работать с однобайтными кодировками, а в консоли Винды до сих пор CP866. Пришлось выводить всё по-английски, писать конвертирующую функцию мне лень.
Объём увеличился, во-первых, из-за улучшений, во-вторых, мне хотелось хранить файлы кешей в каком-нибудь более-менее читаемом формате, чтобы вручную добавить в кеш татарстанские праздники, так что часть объёма (класс «date и методы работы с ним) как раз для этого.
Так вроде всегда так было. На Stack Overflow постоянно встречается why is the Go compiled version is relatively huge?
А сколько такой будет весить на маке?
Комментарий для rodem:
Меня печалит, что с каждой версией всё больше. Моя программа makecorner ( https://github.com/bolknote/MakeCorner ) занимала, скомпилированная версией r60.3 1,6Мб, версия 1.0 компилирует уже 2,2!
Какой такой? Не понял.
Комментарий для rodem:
Upx — наше всё. Тем более, я добился-таки от автора, чтобы он починил его и теперь бинарники, скомпилированные Гоу, им тоже сжимаются.
Комментарий для Евгения Степанищева:
Вот собственно и объяснение. В каком-то видео давнишнем, Роб Пайк говорил, что размер это не главное, главное это скорость.
Комментарий для rodem:
Ну, там точно не всё линкуется статически. Вот мой makecorner, который я уже упоминал:
Комментарий для rodem:
А программа, которая в этом после, слинковалась с неожиданными для меня библиотеками:
аплодирую стоя вашей жене
chcp 65001
Комментарий для zg.livejournal.com:
Знать бы как это сделать из программы на «Гоу» :)
Комментарий для Евгения Степанищева:
Хм, а сколько заняла программа, скомпилированная PyInstaller’ом?
Вроде бы у скомпилированных программ на Питоне примерно такой же объем?
Комментарий для n-baksalyar.ya.ru:
Больше заняла, но там интерпретатор Пайтона целиком лежит.
Товарищи, хватит жаловаться! Да, ваша программа уже не поместится на дискетку. Но у вас отличный язык программирования, отличная стандартная библиотека и очень неплохой garbage collector, да ещё и goroutines! И всё это компилируется моментально, запускается молниеносно, работает прекрасно. Я каждый раз удивляюсь, написав что-нибудь на Go, что это работает, что это так просто и без танцев с бубном. Жаловаться на размер программ уже просто смешно, т. к. это вас отвлекает от всего прекрасного, что вам даёт Golang.
Если вас напрягает то, что вам приходится электронной почтой отправлять такие большие файлы, то вот вам решение: отправляйте ссылку на установщик Golang и исходник программы (он ведь вообще крохотный), с батником «go run src.go». Заодно жена сможет что-нибудь подправить :)
Комментарий для Доброжелатель:
Ну почему же хватит? Должны же быть какие-то разумные пределы?
Она не умеет программировать.