func do(ctx context.Context, notif notifier, pub publisher.Interface, tmpl string) {
	log.Printf("[INFO] message template - %q", tmpl)
	ch := notif.Go(ctx)
	for event := range ch {
		err := pub.Publish(event, func(r rss.Event) string {
			b1 := bytes.Buffer{}
			if err := template.Must(template.New("twi").Parse(tmpl)).Execute(&b1, event); err != nil {  
				return fmt.Sprintf("%s - %s", r.Title, r.Link)
			}
			return strings.Replace(format(b1.String(), 275), `\n`, "\n", -1)  
		})
		if err != nil {
			log.Printf("[WARN] failed to publish, %s", err)
		}
	}
}
do runs event loop getting rss events and publishing them
package main
import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"runtime"
	"strings"
	"syscall"
	"text/template"
	"time"
	"github.com/denisbrodbeck/striphtmltags"
	log "github.com/go-pkgz/lgr"
	"github.com/umputun/go-flags"
	"github.com/umputun/rss2twitter/app/publisher"
	"github.com/umputun/rss2twitter/app/rss"
)
type opts struct {
	Refresh time.Duration `short:"r" long:"refresh" env:"REFRESH" default:"30s" description:"refresh interval"`
	TimeOut time.Duration `short:"t" long:"timeout" env:"TIMEOUT" default:"5s" description:"rss feed timeout"`
	Feed    string        `short:"f" long:"feed" env:"FEED" required:"true" description:"rss feed url"`
	ConsumerKey    string `long:"consumer-key" env:"TWI_CONSUMER_KEY" description:"twitter consumer key"`
	ConsumerSecret string `long:"consumer-secret" env:"TWI_CONSUMER_SECRET" description:"twitter consumer secret"`
	AccessToken    string `long:"access-token" env:"TWI_ACCESS_TOKEN" description:"twitter access token"`
	AccessSecret   string `long:"access-secret" env:"TWI_ACCESS_SECRET" description:"twitter access secret"`
	Template string `long:"template" env:"TEMPLATE" default:"{{.Title}} - {{.Link}}" description:"twitter message template"`
	Dry      bool   `long:"dry" env:"DRY" description:"dry mode"`
	Dbg      bool   `long:"dbg" env:"DEBUG" description:"debug mode"`
}
var revision = "unknown"
type notifier interface {
	Go(ctx context.Context) <-chan rss.Event
}
func main() {
	fmt.Printf("rss2twitter - %s\n", revision)
	o := opts{}
	if _, err := flags.Parse(&o); err != nil {
		os.Exit(1)
	}
	if o.Dbg {
		log.Setup(log.Debug)
	}
	catchSignals()
	notif, pub, err := setup(o)
	if err != nil {
		log.Printf("[PANIC] failed to setup, %v", err)
	}
	ctx, cancel := context.WithCancel(context.Background())
	go func() {  
		stop := make(chan os.Signal, 1)
		signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
		<-stop
		log.Printf("[WARN] interrupt signal")
		cancel()
	}()
	do(ctx, notif, pub, o.Template)
	log.Print("[INFO] terminated")
}
func setup(o opts) (n notifier, p publisher.Interface, err error) {
	content, err := ioutil.ReadFile("exclusion-patterns.txt")
	if err != nil {
		log.Printf("[WARN] could not read 'exclusion-patterns.txt' file: %v", err)
		content = []byte{}
	}
	lines := strings.Split(string(content), "\n")
	n = &rss.Notify{Feed: o.Feed, Duration: o.Refresh, Timeout: o.TimeOut}
	p = publisher.Twitter{
		ConsumerKey:    o.ConsumerKey,
		ConsumerSecret: o.ConsumerSecret,
		AccessToken:    o.AccessToken,
		AccessSecret:   o.AccessSecret,
		ExcludeList:    lines,
	}
	if o.Dry {  
		p = publisher.Stdout{
			ExcludeList: lines,
		}
		log.Print("[INFO] dry mode")
	}
	if !o.Dry && (o.ConsumerKey == "" || o.ConsumerSecret == "" || o.AccessToken == "" || o.AccessSecret == "") {
		return n, p, errors.New("token credentials missing")
	}
	return n, p, nil
}