<commit_before>package main

import (
	"reflect"
	"testing"
)

func TestUserGitconfigMap_EmptyString(t *testing.T) {
	d := ""
	userGitconfigInfo := userGitconfigMap(d)
	if len(userGitconfigInfo) != 0 {
		t.Fatal("expected map with no keys")
	}
}

func TestUserGitconfigMap(t *testing.T) {
	d := `push.default=simple
	merge.conflictstyle=diff3
	pull.ff=only
	core.repositoryformatversion=0
	core.filemode=true
	core.bare=false`
	expected := map[string]string{
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}

	userGitconfigInfo := userGitconfigMap(d)
	if len(userGitconfigInfo) != 6 {
		t.Fatal("got map with unexpected number of keys")
	}
	if !reflect.DeepEqual(userGitconfigInfo, expected) {
		t.Fatal("result does not match expected structure")
	}
}

func TestUserGitconfigMap_WithEmptyLines(t *testing.T) {
	d := `
	user.name=Jane Doe
	push.default=simple
	merge.conflictstyle=diff3
	pull.ff=only

	core.repositoryformatversion=0
	core.filemode=true
	core.bare=false

	`
	expected := map[string]string{
		"user.name":                    "Jane Doe",
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	userGitconfigInfo := userGitconfigMap(d)

	if len(userGitconfigInfo) != 7 {
		t.Fatal("got map with unexpected number of keys")
	}
	if !reflect.DeepEqual(userGitconfigInfo, expected) {
		t.Fatal("result does not match expected structure")
	}
}

func TestUserGitconfigMap_WithNoKeys(t *testing.T) {
	d := `
	`
	expected := make(map[string]string)

	userGitconfigInfo := userGitconfigMap(d)
	if len(userGitconfigInfo) != 0 {
		t.Fatal("expected map with no keys")
	}
	if !reflect.DeepEqual(userGitconfigInfo, expected) {
		t.Fatal("result does not match expected structure")
	}
}

func TestGetCommitAuthor_BothNameAndEmail(t *testing.T) {
	input := map[string]string{
		"user.name":                    "Jane Doe",
		"user.email":                   "jd@j.d",
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	checkAuthor(t, input, "Jane Doe <jd@j.d>")
}

func TestGetCommitAuthor_OnlyName(t *testing.T) {
	input := map[string]string{
		"user.name":                    "Jane Doe",
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	checkAuthor(t, input, "Jane Doe")
}

func TestGetCommitAuthor_OnlyEmail(t *testing.T) {
	input := map[string]string{
		"user.email":                   "jd@j.d",
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	checkAuthor(t, input, "jd@j.d")
}

func TestGetCommitAuthor_NoNameNoEmail(t *testing.T) {
	input := map[string]string{
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	checkAuthor(t, input, "")
}

func TestGetCommitAuthor_NameAndEmptyEmail(t *testing.T) {
	input := map[string]string{
		"user.name":                    "Jane Doe",
		"user.email":                   "",
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	checkAuthor(t, input, "Jane Doe")
}

func TestGetCommitAuthor_EmailAndEmptyName(t *testing.T) {
	input := map[string]string{
		"user.name":                    "",
		"user.email":                   "jd@j.d",
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	checkAuthor(t, input, "jd@j.d")
}

func checkAuthor(t *testing.T, input map[string]string, expected string) {
	author := getCommitAuthor(input)
	if author != expected {
		t.Fatal("author did not match expected value")
	}
}
<commit_msg>more informative error message in unit test<commit_after>package main

import (
	"reflect"
	"testing"
)

func TestUserGitconfigMap_EmptyString(t *testing.T) {
	d := ""
	userGitconfigInfo := userGitconfigMap(d)
	if len(userGitconfigInfo) != 0 {
		t.Fatal("expected map with no keys")
	}
}

func TestUserGitconfigMap(t *testing.T) {
	d := `push.default=simple
	merge.conflictstyle=diff3
	pull.ff=only
	core.repositoryformatversion=0
	core.filemode=true
	core.bare=false`
	expected := map[string]string{
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}

	userGitconfigInfo := userGitconfigMap(d)
	if len(userGitconfigInfo) != 6 {
		t.Fatal("got map with unexpected number of keys")
	}
	if !reflect.DeepEqual(userGitconfigInfo, expected) {
		t.Fatal("result does not match expected structure")
	}
}

func TestUserGitconfigMap_WithEmptyLines(t *testing.T) {
	d := `
	user.name=Jane Doe
	push.default=simple
	merge.conflictstyle=diff3
	pull.ff=only

	core.repositoryformatversion=0
	core.filemode=true
	core.bare=false

	`
	expected := map[string]string{
		"user.name":                    "Jane Doe",
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	userGitconfigInfo := userGitconfigMap(d)

	if len(userGitconfigInfo) != 7 {
		t.Fatal("got map with unexpected number of keys")
	}
	if !reflect.DeepEqual(userGitconfigInfo, expected) {
		t.Fatal("result does not match expected structure")
	}
}

func TestUserGitconfigMap_WithNoKeys(t *testing.T) {
	d := `
	`
	expected := make(map[string]string)

	userGitconfigInfo := userGitconfigMap(d)
	if len(userGitconfigInfo) != 0 {
		t.Fatal("expected map with no keys")
	}
	if !reflect.DeepEqual(userGitconfigInfo, expected) {
		t.Fatal("result does not match expected structure")
	}
}

func TestGetCommitAuthor_BothNameAndEmail(t *testing.T) {
	input := map[string]string{
		"user.name":                    "Jane Doe",
		"user.email":                   "jd@j.d",
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	checkAuthor(t, input, "Jane Doe <jd@j.d>")
}

func TestGetCommitAuthor_OnlyName(t *testing.T) {
	input := map[string]string{
		"user.name":                    "Jane Doe",
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	checkAuthor(t, input, "Jane Doe")
}

func TestGetCommitAuthor_OnlyEmail(t *testing.T) {
	input := map[string]string{
		"user.email":                   "jd@j.d",
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	checkAuthor(t, input, "jd@j.d")
}

func TestGetCommitAuthor_NoNameNoEmail(t *testing.T) {
	input := map[string]string{
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	checkAuthor(t, input, "")
}

func TestGetCommitAuthor_NameAndEmptyEmail(t *testing.T) {
	input := map[string]string{
		"user.name":                    "Jane Doe",
		"user.email":                   "",
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	checkAuthor(t, input, "Jane Doe")
}

func TestGetCommitAuthor_EmailAndEmptyName(t *testing.T) {
	input := map[string]string{
		"user.name":                    "",
		"user.email":                   "jd@j.d",
		"push.default":                 "simple",
		"merge.conflictstyle":          "diff3",
		"pull.ff":                      "only",
		"core.repositoryformatversion": "0",
		"core.filemode":                "true",
		"core.bare":                    "false",
	}
	checkAuthor(t, input, "jd@j.d")
}

func checkAuthor(t *testing.T, input map[string]string, expected string) {
	author := getCommitAuthor(input)
	if author != expected {
		t.Fatalf("author %q does not match expected value %q", author, expected)
	}
}
<|endoftext|>


<commit_before>package command

import (
	"flag"
	"fmt"
	"strings"

	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/color"
)

type Sample struct {
	Ui  cli.Ui
	Cmd string

	mode string
}

func (this *Sample) Run(args []string) (exitCode int) {
	cmdFlags := flag.NewFlagSet("sample", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.StringVar(&this.mode, "mode", "c", "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	var code string
	switch this.mode {
	case "c":
		code = this.consumeSample()

	case "p":
		code = this.produceSample()
	}
	this.Ui.Output(strings.TrimSpace(code))

	return
}

func (*Sample) produceSample() string {
	return fmt.Sprintf(`
public class KafkaProducer {
    private final Producer<String, String> producer;
    public final static String TOPIC = "TEST-TOPIC";

    private KafkaProducer(){
        Properties props = new Properties();
        props.put("%s", "k10101a.mycorp.kfk.com:10101,k10101b.mycorp.kfk.com:10101");
        props.put("serializer.class", "kafka.serializer.StringEncoder");
        props.put("key.serializer.class", "kafka.serializer.StringEncoder");

        // %s
        // 0, means that the producer never waits for an acknowledgement from the broker
        // 1, means that the producer gets an acknowledgement after the leader replica has received the data
        //-1, means that the producer gets an acknowledgement after all in-sync replicas have received the data
        props.put("%s", "1"); // 1 is enough in most cases

        producer = new Producer<String, String>(new ProducerConfig(props));
    }

    void produce() {
        int messageNo = 1000;
        final int COUNT = 10000;

        while (messageNo < COUNT) {
            String key = String.valueOf(messageNo);
            String data = "hello kafka message " + key;
            producer.send(new KeyedMessage<String, String>(TOPIC, key, data));
            messageNo ++;
        }
    }

    public static void main(String[] args) {
        new KafkaProducer().produce();
    }
}
        `,
		color.Cyan("metadata.broker.list"),
		color.Cyan("request.required.acks"),
		color.Cyan("request.required.acks"))
}

func (*Sample) consumeSample() string {
	return fmt.Sprintf(`
public class KafkaConsumer {
    private final ConsumerConnector consumer;

    private KafkaConsumer() {
        Properties props = new Properties();
        props.put("%s", "zk2181a.mycorp.zk.com:2181,zk2181b.mycorp.zk.com:2181,zk2181c.mycorp.zk.com:2181/kafka");
        props.put("%s", "group1");
        props.put("zookeeper.session.timeout.ms", "4000");
        props.put("zookeeper.sync.time.ms", "200");
        props.put("auto.commit.interval.ms", "60000");   // 1m
        //props.put("auto.offset.reset", "smallest");    // largest | smallest
        props.put("serializer.class", "kafka.serializer.StringEncoder");
        ConsumerConfig config = new ConsumerConfig(props);

        consumer = kafka.consumer.Consumer.createJavaConsumerConnector(config);
    }

    public void shutdown() {
        if (consumer != null) {
            consumer.shutdown();
        }
   }

    void consume(String topic, int %s) {
        // %s
        // %s
        // %s
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                consumer.shutdown();
            }
        });

        Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
        topicCountMap.put(topic, %s);

        StringDecoder keyDecoder = new StringDecoder(new VerifiableProperties());
        StringDecoder valueDecoder = new StringDecoder(new VerifiableProperties());

        Map<String, List<KafkaStream<String, String>>> consumerMap =
                consumer.createMessageStreams(topicCountMap, keyDecoder, valueDecoder);
        KafkaStream<String, String> stream = consumerMap.get(topic).get(0);
        ConsumerIterator<String, String> it = stream.iterator();
        while (it.hasNext()) {
            // consumer.commitOffsets(); // manually commit offsets
            System.out.println(it.next().message());
        }
    }

    public static void main(String[] args) {
        new KafkaConsumer().consume();
    }
}
        `,
		color.Cyan("zookeeper.connect"),
		color.Cyan("group.id"),
		color.Green("threads"),
		color.Red("VERY important!"),
		color.Red("graceful shutdown the consumer group to commit consumed offset"),
		color.Red("avoid consuming duplicated message when restarting the same consumer group"),
		color.Green("threads"))
}

func (*Sample) Synopsis() string {
	return "Java sample code of producer/consumer"
}

func (this *Sample) Help() string {
	help := fmt.Sprintf(`
Usage: %s sample -mode mode

    %s

Options:

    -mode <c|p>
      c: consumer
      p: producer

`, this.Cmd, this.Synopsis())
	return strings.TrimSpace(help)
}
<commit_msg>sample code given: how to consume with kafka0.8.2.2+<commit_after>package command

import (
	"flag"
	"fmt"
	"strings"

	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/color"
)

type Sample struct {
	Ui  cli.Ui
	Cmd string

	mode string
}

func (this *Sample) Run(args []string) (exitCode int) {
	cmdFlags := flag.NewFlagSet("sample", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.StringVar(&this.mode, "mode", "c", "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	var code string
	switch this.mode {
	case "c":
		code = this.consumeSample()

	case "o":
		code = this.offsetCommitFetchSample()

	case "p":
		code = this.produceSample()
	}
	this.Ui.Output(strings.TrimSpace(code))

	return
}

func (*Sample) offsetCommitFetchSample() string {
	return fmt.Sprintf(`
KafkaConsumer<K, V> consumer = new KafkaConsumer<K, V>(properties);
...
TopicPartition p0 = new TopicPartition("topic1", 0);
TopicPartition p1 = new TopicPartition("topic1", 1);
%s

Map<TopicPartition, Long> offsets = new LinkedHashMap<TopicPartition, Long>();
offsets.Put(p0, 123L);
offsets.Put(p1, 4564L);

%s
consumer.commit(offsets, CommitType.SYNC);

%s
long committedOffset = consumer.committed(p1);
        `,
		color.Cyan("consumer.subscribe(p0, p1);"),
		color.Cyan("// commit offsets"),
		color.Cyan("//fetch offsets"))
}

func (*Sample) produceSample() string {
	return fmt.Sprintf(`
public class KafkaProducer {
    private final Producer<String, String> producer;
    public final static String TOPIC = "TEST-TOPIC";

    private KafkaProducer(){
        Properties props = new Properties();
        props.put("%s", "k10101a.mycorp.kfk.com:10101,k10101b.mycorp.kfk.com:10101");
        props.put("serializer.class", "kafka.serializer.StringEncoder");
        props.put("key.serializer.class", "kafka.serializer.StringEncoder");

        // %s
        // 0, means that the producer never waits for an acknowledgement from the broker
        // 1, means that the producer gets an acknowledgement after the leader replica has received the data
        //-1, means that the producer gets an acknowledgement after all in-sync replicas have received the data
        props.put("%s", "1"); // 1 is enough in most cases

        producer = new Producer<String, String>(new ProducerConfig(props));
    }

    void produce() {
        int messageNo = 1000;
        final int COUNT = 10000;

        while (messageNo < COUNT) {
            String key = String.valueOf(messageNo);
            String data = "hello kafka message " + key;
            producer.send(new KeyedMessage<String, String>(TOPIC, key, data));
            messageNo ++;
        }
    }

    public static void main(String[] args) {
        new KafkaProducer().produce();
    }
}
        `,
		color.Cyan("metadata.broker.list"),
		color.Cyan("request.required.acks"),
		color.Cyan("request.required.acks"))
}

func (*Sample) consumeSample() string {
	return fmt.Sprintf(`
public class KafkaConsumer {
    private final ConsumerConnector consumer;

    private KafkaConsumer() {
        Properties props = new Properties();
        props.put("%s", "zk2181a.mycorp.zk.com:2181,zk2181b.mycorp.zk.com:2181,zk2181c.mycorp.zk.com:2181/kafka");
        props.put("%s", "group1");
        props.put("zookeeper.session.timeout.ms", "4000");
        props.put("zookeeper.sync.time.ms", "200");
        props.put("auto.commit.interval.ms", "60000");   // 1m
        //props.put("auto.offset.reset", "smallest");    // largest | smallest
        props.put("serializer.class", "kafka.serializer.StringEncoder");
        ConsumerConfig config = new ConsumerConfig(props);

        consumer = kafka.consumer.Consumer.createJavaConsumerConnector(config);
    }

    public void shutdown() {
        if (consumer != null) {
            consumer.shutdown();
        }
   }

    void consume(String topic, int %s) {
        // %s
        // %s
        // %s
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                consumer.shutdown();
            }
        });

        Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
        topicCountMap.put(topic, %s);

        StringDecoder keyDecoder = new StringDecoder(new VerifiableProperties());
        StringDecoder valueDecoder = new StringDecoder(new VerifiableProperties());

        Map<String, List<KafkaStream<String, String>>> consumerMap =
                consumer.createMessageStreams(topicCountMap, keyDecoder, valueDecoder);
        KafkaStream<String, String> stream = consumerMap.get(topic).get(0);
        ConsumerIterator<String, String> it = stream.iterator();
        while (it.hasNext()) {
            // consumer.commitOffsets(); // manually commit offsets
            System.out.println(it.next().message());
        }
    }

    public static void main(String[] args) {
        new KafkaConsumer().consume();
    }
}
        `,
		color.Cyan("zookeeper.connect"),
		color.Cyan("group.id"),
		color.Green("threads"),
		color.Red("VERY important!"),
		color.Red("graceful shutdown the consumer group to commit consumed offset"),
		color.Red("avoid consuming duplicated message when restarting the same consumer group"),
		color.Green("threads"))
}

func (*Sample) Synopsis() string {
	return "Java sample code of producer/consumer"
}

func (this *Sample) Help() string {
	help := fmt.Sprintf(`
Usage: %s sample -mode mode

    %s

Options:

    -mode <c|p|o>
      c: consumer
      p: producer
      o: consumer offsets managed by kafka instead of zk

`, this.Cmd, this.Synopsis())
	return strings.TrimSpace(help)
}
<|endoftext|>


<commit_before>package command

import (
	"bufio"
	"flag"
	"fmt"
	"sort"
	"strings"

	"github.com/funkygao/gafka/config"
	"github.com/funkygao/gafka/zk"
	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/color"
	"github.com/funkygao/golib/pipestream"
	"github.com/funkygao/sarama"
)

type Topics struct {
	Ui  cli.Ui
	Cmd string
}

func (this *Topics) Run(args []string) (exitCode int) {
	var (
		zone        string
		cluster     string
		topicPrefix string
		verbose     bool
		addTopic    string
		replicas    int
		partitions  int
		topicConfig string
	)
	cmdFlags := flag.NewFlagSet("brokers", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.StringVar(&zone, "z", "", "")
	cmdFlags.StringVar(&topicPrefix, "t", "", "")
	cmdFlags.StringVar(&cluster, "c", "", "")
	cmdFlags.BoolVar(&verbose, "verbose", false, "")
	cmdFlags.StringVar(&addTopic, "add", "", "")
	cmdFlags.IntVar(&partitions, "partitions", 1, "")
	cmdFlags.StringVar(&topicConfig, "config", "", "")
	cmdFlags.IntVar(&replicas, "replicas", 2, "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	if validateArgs(this, this.Ui).require("-z").on("-add", "-c").invalid(args) {
		return 2
	}

	if addTopic != "" {
		zkzone := zk.NewZkZone(zk.DefaultConfig(zone, config.ZonePath(zone)))
		zkcluster := zkzone.NewCluster(cluster)
		this.addTopic(zkcluster, addTopic, replicas, partitions)

		return
	}

	ensureZoneValid(zone)

	zkzone := zk.NewZkZone(zk.DefaultConfig(zone, config.ZonePath(zone)))
	if cluster != "" {
		this.displayTopicsOfCluster(cluster, zkzone, topicPrefix, verbose)
		return
	}

	// all clusters
	zkzone.WithinClusters(func(cluster string, path string) {
		this.displayTopicsOfCluster(cluster, zkzone, topicPrefix, verbose)
	})

	return
}

func (this *Topics) displayTopicsOfCluster(cluster string, zkzone *zk.ZkZone,
	topicPrefix string, verbose bool) {
	must := func(err error) {
		if err != nil {
			panic(err)
		}
	}

	if verbose {
		this.Ui.Output(cluster)
	}

	zkcluster := zkzone.NewCluster(cluster)

	// get all alive brokers within this cluster
	brokers := zkcluster.Brokers()
	if len(brokers) == 0 {
		this.Ui.Output(fmt.Sprintf("%4s%s", " ", color.Red("empty brokers")))
		return
	}

	if verbose {
		sortedBrokerIds := make([]string, 0, len(brokers))
		for brokerId, _ := range brokers {
			sortedBrokerIds = append(sortedBrokerIds, brokerId)
		}
		sort.Strings(sortedBrokerIds)
		for _, brokerId := range sortedBrokerIds {
			this.Ui.Output(fmt.Sprintf("%4s%s %s", " ", color.Green(brokerId),
				brokers[brokerId]))
		}
	}

	// find 1st broker in the cluster
	// each broker in the cluster has same metadata
	var broker0 *zk.Broker
	for _, broker := range brokers {
		broker0 = broker
		break
	}

	kfk, err := sarama.NewClient([]string{broker0.Addr()}, sarama.NewConfig())
	if err != nil {
		if verbose {
			this.Ui.Output(color.Yellow("%5s%s %s", " ", broker0.Addr(),
				err.Error()))
		}

		return
	}
	defer kfk.Close()

	topics, err := kfk.Topics()
	must(err)
	if len(topics) == 0 {
		if topicPrefix == "" && verbose {
			this.Ui.Output(fmt.Sprintf("%5s%s", " ", color.Magenta("no topics")))
		}

		return
	}

	for _, topic := range topics {
		if topicPrefix != "" && !strings.HasPrefix(topic, topicPrefix) {
			continue
		}

		if verbose {
			this.Ui.Output(strings.Repeat(" ", 4) + color.Blue(topic))
		}

		// get partitions and check if some dead
		alivePartitions, err := kfk.WritablePartitions(topic)
		must(err)
		partions, err := kfk.Partitions(topic)
		must(err)
		if len(alivePartitions) != len(partions) {
			this.Ui.Output(fmt.Sprintf("topic[%s] has %s partitions: %+v/%+v",
				alivePartitions, color.Red("dead"), partions))
		}

		if !verbose {
			this.Ui.Output(fmt.Sprintf("%30s %s %3dP",
				cluster,
				color.Blue("%50s", topic),
				len(partions)))
			continue
		}

		for _, partitionID := range alivePartitions {
			leader, err := kfk.Leader(topic, partitionID)
			must(err)

			replicas, err := kfk.Replicas(topic, partitionID)
			must(err)

			isr, err := kfk.Isr(topic, partitionID)
			must(err)

			underReplicated := false
			if len(isr) != len(replicas) {
				underReplicated = true
			}

			latestOffset, err := kfk.GetOffset(topic, partitionID,
				sarama.OffsetNewest)
			must(err)

			oldestOffset, err := kfk.GetOffset(topic, partitionID,
				sarama.OffsetOldest)
			must(err)

			if !underReplicated {
				this.Ui.Output(fmt.Sprintf("%8d Leader:%d Replicas:%+v Isr:%+v Offset:%d Num:%d",
					partitionID, leader.ID(), replicas, isr,
					latestOffset, latestOffset-oldestOffset))
			} else {
				// use red for alert
				this.Ui.Output(color.Red("%8d Leader:%d Replicas:%+v Isr:%+v Offset:%d Num:%d",
					partitionID, leader.ID(), replicas, isr,
					latestOffset, latestOffset-oldestOffset))
			}

		}
	}
}

func (this *Topics) addTopic(zkcluster *zk.ZkCluster, topic string, replicas,
	partitions int) error {
	this.Ui.Info(fmt.Sprintf("creating kafka topic: %s", topic))

	zkAddrs := zkcluster.ZkAddrs()

	cmd := pipestream.New(fmt.Sprintf("%s/bin/kafka-topics.sh", config.KafkaHome()),
		fmt.Sprintf("--zookeeper %s", zkAddrs),
		fmt.Sprintf("--create"),
		fmt.Sprintf("--topic %s", topic),
		fmt.Sprintf("--partitions %d", partitions),
		fmt.Sprintf("--replication-factor %d", replicas),
	)
	err := cmd.Open()
	if err != nil {
		return err
	}

	scanner := bufio.NewScanner(cmd.Reader())
	scanner.Split(bufio.ScanLines)
	var errmsg string
	var line string
	for scanner.Scan() {
		line = scanner.Text()

		this.Ui.Info(line)
		if strings.HasPrefix(line, "Error") {
			errmsg = line
		}
	}
	err = scanner.Err()
	if err != nil {
		return err
	}
	cmd.Close()

	if errmsg != "" {
		return fmt.Errorf("%s", errmsg)
	}

	this.Ui.Output(fmt.Sprintf("\tzk: %s", zkAddrs))
	this.Ui.Output(fmt.Sprintf("\tbroker list: %s",
		strings.Join(zkcluster.BrokerList(), ",")))
	return nil
}

func (*Topics) Synopsis() string {
	return "Manage topics & partitions of a zone"
}

func (this *Topics) Help() string {
	help := fmt.Sprintf(`
Usage: %s topics -z zone [options]

	Manage topics & partitions of a zone

Options:

  -c cluster

  -t topic prefix
  	Only show topics like this give topic.

  -config k=v
  	Config a topic. TODO

  -add topic
  	Add a topic to a kafka cluster.

  -partitions n

  -replicas n

  -verbose
`, this.Cmd)
	return strings.TrimSpace(help)
}
<commit_msg>output alignment<commit_after>package command

import (
	"bufio"
	"flag"
	"fmt"
	"sort"
	"strings"

	"github.com/funkygao/gafka/config"
	"github.com/funkygao/gafka/zk"
	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/color"
	"github.com/funkygao/golib/pipestream"
	"github.com/funkygao/sarama"
)

type Topics struct {
	Ui  cli.Ui
	Cmd string
}

func (this *Topics) Run(args []string) (exitCode int) {
	var (
		zone        string
		cluster     string
		topicPrefix string
		verbose     bool
		addTopic    string
		replicas    int
		partitions  int
		topicConfig string
	)
	cmdFlags := flag.NewFlagSet("brokers", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.StringVar(&zone, "z", "", "")
	cmdFlags.StringVar(&topicPrefix, "t", "", "")
	cmdFlags.StringVar(&cluster, "c", "", "")
	cmdFlags.BoolVar(&verbose, "verbose", false, "")
	cmdFlags.StringVar(&addTopic, "add", "", "")
	cmdFlags.IntVar(&partitions, "partitions", 1, "")
	cmdFlags.StringVar(&topicConfig, "config", "", "")
	cmdFlags.IntVar(&replicas, "replicas", 2, "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	if validateArgs(this, this.Ui).require("-z").on("-add", "-c").invalid(args) {
		return 2
	}

	if addTopic != "" {
		zkzone := zk.NewZkZone(zk.DefaultConfig(zone, config.ZonePath(zone)))
		zkcluster := zkzone.NewCluster(cluster)
		this.addTopic(zkcluster, addTopic, replicas, partitions)

		return
	}

	ensureZoneValid(zone)

	zkzone := zk.NewZkZone(zk.DefaultConfig(zone, config.ZonePath(zone)))
	if cluster != "" {
		this.displayTopicsOfCluster(cluster, zkzone, topicPrefix, verbose)
		return
	}

	// all clusters
	zkzone.WithinClusters(func(cluster string, path string) {
		this.displayTopicsOfCluster(cluster, zkzone, topicPrefix, verbose)
	})

	return
}

func (this *Topics) displayTopicsOfCluster(cluster string, zkzone *zk.ZkZone,
	topicPrefix string, verbose bool) {
	must := func(err error) {
		if err != nil {
			panic(err)
		}
	}

	if verbose {
		this.Ui.Output(cluster)
	}

	zkcluster := zkzone.NewCluster(cluster)

	// get all alive brokers within this cluster
	brokers := zkcluster.Brokers()
	if len(brokers) == 0 {
		this.Ui.Output(fmt.Sprintf("%4s%s", " ", color.Red("empty brokers")))
		return
	}

	if verbose {
		sortedBrokerIds := make([]string, 0, len(brokers))
		for brokerId, _ := range brokers {
			sortedBrokerIds = append(sortedBrokerIds, brokerId)
		}
		sort.Strings(sortedBrokerIds)
		for _, brokerId := range sortedBrokerIds {
			this.Ui.Output(fmt.Sprintf("%4s%s %s", " ", color.Green(brokerId),
				brokers[brokerId]))
		}
	}

	// find 1st broker in the cluster
	// each broker in the cluster has same metadata
	var broker0 *zk.Broker
	for _, broker := range brokers {
		broker0 = broker
		break
	}

	kfk, err := sarama.NewClient([]string{broker0.Addr()}, sarama.NewConfig())
	if err != nil {
		if verbose {
			this.Ui.Output(color.Yellow("%5s%s %s", " ", broker0.Addr(),
				err.Error()))
		}

		return
	}
	defer kfk.Close()

	topics, err := kfk.Topics()
	must(err)
	if len(topics) == 0 {
		if topicPrefix == "" && verbose {
			this.Ui.Output(fmt.Sprintf("%5s%s", " ", color.Magenta("no topics")))
		}

		return
	}

	for _, topic := range topics {
		if topicPrefix != "" && !strings.HasPrefix(topic, topicPrefix) {
			continue
		}

		if verbose {
			this.Ui.Output(strings.Repeat(" ", 4) + color.Blue(topic))
		}

		// get partitions and check if some dead
		alivePartitions, err := kfk.WritablePartitions(topic)
		must(err)
		partions, err := kfk.Partitions(topic)
		must(err)
		if len(alivePartitions) != len(partions) {
			this.Ui.Output(fmt.Sprintf("topic[%s] has %s partitions: %+v/%+v",
				alivePartitions, color.Red("dead"), partions))
		}

		if !verbose {
			this.Ui.Output(fmt.Sprintf("%30s %s %3dP",
				cluster,
				color.Blue("%50s", topic),
				len(partions)))
			continue
		}

		for _, partitionID := range alivePartitions {
			leader, err := kfk.Leader(topic, partitionID)
			must(err)

			replicas, err := kfk.Replicas(topic, partitionID)
			must(err)

			isr, err := kfk.Isr(topic, partitionID)
			must(err)

			underReplicated := false
			if len(isr) != len(replicas) {
				underReplicated = true
			}

			latestOffset, err := kfk.GetOffset(topic, partitionID,
				sarama.OffsetNewest)
			must(err)

			oldestOffset, err := kfk.GetOffset(topic, partitionID,
				sarama.OffsetOldest)
			must(err)

			if !underReplicated {
				this.Ui.Output(fmt.Sprintf("%8d Leader:%d Replicas:%+v Isr:%+v Offset:%d Num:%d",
					partitionID, leader.ID(), replicas, isr,
					latestOffset, latestOffset-oldestOffset))
			} else {
				// use red for alert
				this.Ui.Output(color.Red("%8d Leader:%d Replicas:%+v Isr:%+v Offset:%d Num:%d",
					partitionID, leader.ID(), replicas, isr,
					latestOffset, latestOffset-oldestOffset))
			}

		}
	}
}

func (this *Topics) addTopic(zkcluster *zk.ZkCluster, topic string, replicas,
	partitions int) error {
	this.Ui.Info(fmt.Sprintf("creating kafka topic: %s", topic))

	zkAddrs := zkcluster.ZkAddrs()

	cmd := pipestream.New(fmt.Sprintf("%s/bin/kafka-topics.sh", config.KafkaHome()),
		fmt.Sprintf("--zookeeper %s", zkAddrs),
		fmt.Sprintf("--create"),
		fmt.Sprintf("--topic %s", topic),
		fmt.Sprintf("--partitions %d", partitions),
		fmt.Sprintf("--replication-factor %d", replicas),
	)
	err := cmd.Open()
	if err != nil {
		return err
	}

	scanner := bufio.NewScanner(cmd.Reader())
	scanner.Split(bufio.ScanLines)
	var errmsg string
	var line string
	for scanner.Scan() {
		line = scanner.Text()

		this.Ui.Info(line)
		if strings.HasPrefix(line, "Error") {
			errmsg = line
		}
	}
	err = scanner.Err()
	if err != nil {
		return err
	}
	cmd.Close()

	if errmsg != "" {
		return fmt.Errorf("%s", errmsg)
	}

	this.Ui.Output(fmt.Sprintf("\tzookeeper.connect: %s", zkAddrs))
	this.Ui.Output(fmt.Sprintf("\t      broker list: %s",
		strings.Join(zkcluster.BrokerList(), ",")))
	return nil
}

func (*Topics) Synopsis() string {
	return "Manage topics & partitions of a zone"
}

func (this *Topics) Help() string {
	help := fmt.Sprintf(`
Usage: %s topics -z zone [options]

	Manage topics & partitions of a zone

Options:

  -c cluster

  -t topic prefix
  	Only show topics like this give topic.

  -config k=v
  	Config a topic. TODO

  -add topic
  	Add a topic to a kafka cluster.

  -partitions n

  -replicas n

  -verbose
`, this.Cmd)
	return strings.TrimSpace(help)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"testing"
)

func Test_runCreate_nameNotSpecified(t *testing.T) {
	if err := runCreate(cmdCreate, nil); err != errCreateNameNotSpecified {
		t.Errorf("err should be %q [actual: %q]", errCreateNameNotSpecified, err)
	}
}

func Test_runCreate_tooManyArgs(t *testing.T) {
	if err := runCreate(cmdCreate, []string{"test1", "test2"}); err != errCreateTooManyArgs {
		t.Errorf("err should be %q [actual: %q]", errCreateTooManyArgs, err)
	}
}

func Test_runCreate_mkdirErr(t *testing.T) {
	dirname := "testdir"

	defer os.Remove(dirname)

	os.Remove(dirname)

	if err := os.Mkdir(dirname, os.ModePerm); err != nil {
		t.Errorf("error occurred [error: %q]", err)
	}

	errExpected := fmt.Errorf("mkdir %s: file exists", dirname)

	if err := runCreate(cmdCreate, []string{dirname}); err == nil || err.Error() != errExpected.Error() {
		t.Errorf("err should be %q [actual: %q]", errExpected, err)
	}
}

func Test_runCreate(t *testing.T) {
	dirname := "testdir"

	defer os.Remove(dirname)

	os.Remove(dirname)

	if err := runCreate(cmdCreate, []string{dirname}); err != nil {
		t.Errorf("error occurred [error: %q]", err)
	}
}
<commit_msg>Update cmd/gorot/create_test.go<commit_after>package main

import (
	"os"
	"testing"
)

func Test_runCreate_nameNotSpecified(t *testing.T) {
	if err := runCreate(cmdCreate, nil); err != errCreateNameNotSpecified {
		t.Errorf("err should be %q [actual: %q]", errCreateNameNotSpecified, err)
	}
}

func Test_runCreate_tooManyArgs(t *testing.T) {
	if err := runCreate(cmdCreate, []string{"test1", "test2"}); err != errCreateTooManyArgs {
		t.Errorf("err should be %q [actual: %q]", errCreateTooManyArgs, err)
	}
}

func Test_runCreate_mkdirErr(t *testing.T) {
	dirname := "testdir"

	defer os.Remove(dirname)

	os.Remove(dirname)

	if err := os.Mkdir(dirname, os.ModePerm); err != nil {
		t.Errorf("error occurred [error: %q]", err)
	}

	if err := runCreate(cmdCreate, []string{dirname}); err == nil {
		t.Error("error should be occurred")
	}
}

func Test_runCreate(t *testing.T) {
	dirname := "testdir"

	defer os.Remove(dirname)

	os.Remove(dirname)

	if err := runCreate(cmdCreate, []string{dirname}); err != nil {
		t.Errorf("error occurred [error: %q]", err)
	}
}
<|endoftext|>


<commit_before>package golb

import (
	"net/http"
	"regexp"
)

type route struct {
	re      *regexp.Regexp
	handler func(http.ResponseWriter, *http.Request, []string)
}

type ReHandler struct {
	routes []*route
}

func (h *ReHandler) AddRoute(re string, handler func(http.ResponseWriter, *http.Request, []string)) {
	r := &route{regexp.MustCompile(re), handler}
	h.routes = append(h.routes, r)
}

func (h *ReHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	for _, route := range h.routes {
		matches := route.re.FindStringSubmatch(r.URL.Path)
		if matches != nil {
			route.handler(w, r, matches)
			return
		}
	}
	http.NotFound(w, r)
}
<commit_msg>embed type<commit_after>package golb

import (
	"net/http"
	"regexp"
)

type RouteHandler func(http.ResponseWriter, *http.Request, []string)

type route struct {
	re      *regexp.Regexp
	handler RouteHandler
}

type ReHandler struct {
	routes []*route
}

func (h *ReHandler) AddRoute(re string, handler RouteHandler) {
	r := &route{regexp.MustCompile(re), handler}
	h.routes = append(h.routes, r)
}

func (h *ReHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	for _, route := range h.routes {
		matches := route.re.FindStringSubmatch(r.URL.Path)
		if matches != nil {
			route.handler(w, r, matches)
			return
		}
	}
	http.NotFound(w, r)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/grafana/metrictank/clock"
	"github.com/grafana/metrictank/stacktest/graphite"
	"github.com/grafana/metrictank/stats"
	log "github.com/sirupsen/logrus"
	"math"
	"net/http"
	"strconv"
	"time"
)

var (
	httpError    = stats.NewCounter32("parrot.monitoring.error;error=http")
	decodeError  = stats.NewCounter32("parrot.monitoring.error;error=decode")
	invalidError = stats.NewCounter32("parrot.monitoring.error;error=invalid")
)

var metricsBySeries []partitionMetrics

type seriesStats struct {
	lastTs uint32
	//the partition currently being checked
	nans int32
	//the sum of abs(value - ts) across the time series
	deltaSum float64
	//the number of timestamps where value != ts
	numNonMatching int32
	//tracks the last seen non-NaN time stamp (useful for lag
	lastSeen uint32
}

type partitionMetrics struct {
	//number of missing values for each series
	nanCount *stats.Gauge32
	//time since the last value was recorded
	lag *stats.Gauge32
	//total amount of drift between expected value and actual values
	deltaSum *stats.Gauge32
	//total number of entries where drift occurred
	nonMatching *stats.Gauge32
}

func monitor() {
	initMetricsBySeries()
	for tick := range clock.AlignedTickLossless(queryInterval) {

		query := graphite.ExecuteRenderQuery(buildRequest(tick))
		if query.HTTPErr != nil {
			httpError.Inc()
		}
		if query.DecodeErr != nil {
			decodeError.Inc()
		}

		for _, s := range query.Decoded {
			processPartitionSeries(s)
		}
	}
}

func processPartitionSeries(s graphite.Series) {
	log.Infof("%d - %d", s.Datapoints[0].Ts, s.Datapoints[len(s.Datapoints)-1].Ts)
	partition, err := strconv.Atoi(s.Target)
	if err != nil {
		log.Debug("unable to parse partition", err)
		invalidError.Inc()
		return
	}
	serStats := seriesStats{}
	serStats.lastTs = s.Datapoints[len(s.Datapoints)-1].Ts

	for _, dp := range s.Datapoints {

		if math.IsNaN(dp.Val) {
			serStats.nans += 1
			continue
		}
		serStats.lastSeen = dp.Ts
		if diff := dp.Val - float64(dp.Ts); diff != 0 {
			log.Debugf("partition=%d dp.Val=%f dp.Ts=%d diff=%f", partition, dp.Val, dp.Ts, diff)
			serStats.deltaSum += diff
			serStats.numNonMatching += 1
		}
	}

	metrics := metricsBySeries[partition]
	metrics.nanCount.Set(int(serStats.nans))
	metrics.lag.Set(int(serStats.lastTs - serStats.lastSeen))
	metrics.deltaSum.Set(int(serStats.deltaSum))
	metrics.nonMatching.Set(int(serStats.numNonMatching))
}

func initMetricsBySeries() {
	for p := 0; p < int(partitionCount); p++ {
		metrics := partitionMetrics{
			nanCount:    stats.NewGauge32(fmt.Sprintf("parrot.monitoring.nancount;partition=%d", p)),
			lag:         stats.NewGauge32(fmt.Sprintf("parrot.monitoring.lag;partition=%d", p)),
			deltaSum:    stats.NewGauge32(fmt.Sprintf("parrot.monitoring.deltaSum;partition=%d", p)),
			nonMatching: stats.NewGauge32(fmt.Sprintf("parrot.monitoring.nonMatching;partition=%d", p)),
		}
		metricsBySeries = append(metricsBySeries, metrics)
	}
}

func buildRequest(now time.Time) *http.Request {
	req, _ := http.NewRequest("GET", fmt.Sprintf("%s/render", gatewayAddress), nil)
	q := req.URL.Query()
	q.Set("target", "aliasByNode(parrot.testdata.*.generated.*, 2)")
	q.Set("from", strconv.Itoa(int(now.Add(-1*lookbackPeriod).Unix()-1)))
	q.Set("until", strconv.Itoa(int(now.Unix())))
	q.Set("format", "json")
	q.Set("X-Org-Id", strconv.Itoa(orgId))
	req.URL.RawQuery = q.Encode()
	if len(gatewayKey) != 0 {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", gatewayKey))
	}
	return req
}
<commit_msg>break out if we encounter an error<commit_after>package main

import (
	"fmt"
	"github.com/grafana/metrictank/clock"
	"github.com/grafana/metrictank/stacktest/graphite"
	"github.com/grafana/metrictank/stats"
	log "github.com/sirupsen/logrus"
	"math"
	"net/http"
	"strconv"
	"time"
)

var (
	httpError    = stats.NewCounter32("parrot.monitoring.error;error=http")
	decodeError  = stats.NewCounter32("parrot.monitoring.error;error=decode")
	invalidError = stats.NewCounter32("parrot.monitoring.error;error=invalid")
)

var metricsBySeries []partitionMetrics

type seriesStats struct {
	lastTs uint32
	//the partition currently being checked
	nans int32
	//the sum of abs(value - ts) across the time series
	deltaSum float64
	//the number of timestamps where value != ts
	numNonMatching int32
	//tracks the last seen non-NaN time stamp (useful for lag
	lastSeen uint32
}

type partitionMetrics struct {
	//number of missing values for each series
	nanCount *stats.Gauge32
	//time since the last value was recorded
	lag *stats.Gauge32
	//total amount of drift between expected value and actual values
	deltaSum *stats.Gauge32
	//total number of entries where drift occurred
	nonMatching *stats.Gauge32
}

func monitor() {
	initMetricsBySeries()
	for tick := range clock.AlignedTickLossless(queryInterval) {

		query := graphite.ExecuteRenderQuery(buildRequest(tick))
		if query.HTTPErr != nil {
			httpError.Inc()
			continue
		}
		if query.DecodeErr != nil {
			decodeError.Inc()
			continue
		}

		for _, s := range query.Decoded {
			processPartitionSeries(s)
		}
	}
}

func processPartitionSeries(s graphite.Series) {
	log.Infof("%d - %d", s.Datapoints[0].Ts, s.Datapoints[len(s.Datapoints)-1].Ts)
	partition, err := strconv.Atoi(s.Target)
	if err != nil {
		log.Debug("unable to parse partition", err)
		invalidError.Inc()
		return
	}
	serStats := seriesStats{}
	serStats.lastTs = s.Datapoints[len(s.Datapoints)-1].Ts

	for _, dp := range s.Datapoints {

		if math.IsNaN(dp.Val) {
			serStats.nans += 1
			continue
		}
		serStats.lastSeen = dp.Ts
		if diff := dp.Val - float64(dp.Ts); diff != 0 {
			log.Debugf("partition=%d dp.Val=%f dp.Ts=%d diff=%f", partition, dp.Val, dp.Ts, diff)
			serStats.deltaSum += diff
			serStats.numNonMatching += 1
		}
	}

	metrics := metricsBySeries[partition]
	metrics.nanCount.Set(int(serStats.nans))
	metrics.lag.Set(int(serStats.lastTs - serStats.lastSeen))
	metrics.deltaSum.Set(int(serStats.deltaSum))
	metrics.nonMatching.Set(int(serStats.numNonMatching))
}

func initMetricsBySeries() {
	for p := 0; p < int(partitionCount); p++ {
		metrics := partitionMetrics{
			nanCount:    stats.NewGauge32(fmt.Sprintf("parrot.monitoring.nancount;partition=%d", p)),
			lag:         stats.NewGauge32(fmt.Sprintf("parrot.monitoring.lag;partition=%d", p)),
			deltaSum:    stats.NewGauge32(fmt.Sprintf("parrot.monitoring.deltaSum;partition=%d", p)),
			nonMatching: stats.NewGauge32(fmt.Sprintf("parrot.monitoring.nonMatching;partition=%d", p)),
		}
		metricsBySeries = append(metricsBySeries, metrics)
	}
}

func buildRequest(now time.Time) *http.Request {
	req, _ := http.NewRequest("GET", fmt.Sprintf("%s/render", gatewayAddress), nil)
	q := req.URL.Query()
	q.Set("target", "aliasByNode(parrot.testdata.*.generated.*, 2)")
	q.Set("from", strconv.Itoa(int(now.Add(-1*lookbackPeriod).Unix()-1)))
	q.Set("until", strconv.Itoa(int(now.Unix())))
	q.Set("format", "json")
	q.Set("X-Org-Id", strconv.Itoa(orgId))
	req.URL.RawQuery = q.Encode()
	if len(gatewayKey) != 0 {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", gatewayKey))
	}
	return req
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"path/filepath"
	"strings"

	"github.com/AudriusButkevicius/cli"
	"github.com/syncthing/syncthing/lib/config"
)

func init() {
	cliCommands = append(cliCommands, cli.Command{
		Name:     "folders",
		HideHelp: true,
		Usage:    "Folder command group",
		Subcommands: []cli.Command{
			{
				Name:     "list",
				Usage:    "List available folders",
				Requires: &cli.Requires{},
				Action:   foldersList,
			},
			{
				Name:     "add",
				Usage:    "Add a new folder",
				Requires: &cli.Requires{"folder id", "directory"},
				Action:   foldersAdd,
			},
			{
				Name:     "remove",
				Usage:    "Remove an existing folder",
				Requires: &cli.Requires{"folder id"},
				Action:   foldersRemove,
			},
			{
				Name:     "override",
				Usage:    "Override changes from other nodes for a master folder",
				Requires: &cli.Requires{"folder id"},
				Action:   foldersOverride,
			},
			{
				Name:     "get",
				Usage:    "Get a property of a folder",
				Requires: &cli.Requires{"folder id", "property"},
				Action:   foldersGet,
			},
			{
				Name:     "set",
				Usage:    "Set a property of a folder",
				Requires: &cli.Requires{"folder id", "property", "value..."},
				Action:   foldersSet,
			},
			{
				Name:     "unset",
				Usage:    "Unset a property of a folder",
				Requires: &cli.Requires{"folder id", "property"},
				Action:   foldersUnset,
			},
			{
				Name:     "devices",
				Usage:    "Folder devices command group",
				HideHelp: true,
				Subcommands: []cli.Command{
					{
						Name:     "list",
						Usage:    "List of devices which the folder is shared with",
						Requires: &cli.Requires{"folder id"},
						Action:   foldersDevicesList,
					},
					{
						Name:     "add",
						Usage:    "Share a folder with a device",
						Requires: &cli.Requires{"folder id", "device id"},
						Action:   foldersDevicesAdd,
					},
					{
						Name:     "remove",
						Usage:    "Unshare a folder with a device",
						Requires: &cli.Requires{"folder id", "device id"},
						Action:   foldersDevicesRemove,
					},
					{
						Name:     "clear",
						Usage:    "Unshare a folder with all devices",
						Requires: &cli.Requires{"folder id"},
						Action:   foldersDevicesClear,
					},
				},
			},
		},
	})
}

func foldersList(c *cli.Context) {
	cfg := getConfig(c)
	first := true
	writer := newTableWriter()
	for _, folder := range cfg.Folders {
		if !first {
			fmt.Fprintln(writer)
		}
		fmt.Fprintln(writer, "ID:\t", folder.ID, "\t")
		fmt.Fprintln(writer, "Path:\t", folder.RawPath, "\t(directory)")
		fmt.Fprintln(writer, "Folder type:\t", folder.Type, "\t(type)")
		fmt.Fprintln(writer, "Ignore permissions:\t", folder.IgnorePerms, "\t(permissions)")
		fmt.Fprintln(writer, "Rescan interval in seconds:\t", folder.RescanIntervalS, "\t(rescan)")

		if folder.Versioning.Type != "" {
			fmt.Fprintln(writer, "Versioning:\t", folder.Versioning.Type, "\t(versioning)")
			for key, value := range folder.Versioning.Params {
				fmt.Fprintf(writer, "Versioning %s:\t %s \t(versioning-%s)\n", key, value, key)
			}
		}
		first = false
	}
	writer.Flush()
}

func foldersAdd(c *cli.Context) {
	cfg := getConfig(c)
	abs, err := filepath.Abs(c.Args()[1])
	die(err)
	folder := config.FolderConfiguration{
		ID:      c.Args()[0],
		RawPath: filepath.Clean(abs),
	}
	cfg.Folders = append(cfg.Folders, folder)
	setConfig(c, cfg)
}

func foldersRemove(c *cli.Context) {
	cfg := getConfig(c)
	rid := c.Args()[0]
	for i, folder := range cfg.Folders {
		if folder.ID == rid {
			last := len(cfg.Folders) - 1
			cfg.Folders[i] = cfg.Folders[last]
			cfg.Folders = cfg.Folders[:last]
			setConfig(c, cfg)
			return
		}
	}
	die("Folder " + rid + " not found")
}

func foldersOverride(c *cli.Context) {
	cfg := getConfig(c)
	rid := c.Args()[0]
	for _, folder := range cfg.Folders {
		if folder.ID == rid && folder.Type == config.FolderTypeReadOnly {
			response := httpPost(c, "db/override", "")
			if response.StatusCode != 200 {
				err := fmt.Sprint("Failed to override changes\nStatus code: ", response.StatusCode)
				body := string(responseToBArray(response))
				if body != "" {
					err += "\nBody: " + body
				}
				die(err)
			}
			return
		}
	}
	die("Folder " + rid + " not found or folder not master")
}

func foldersGet(c *cli.Context) {
	cfg := getConfig(c)
	rid := c.Args()[0]
	arg := strings.ToLower(c.Args()[1])
	for _, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		if strings.HasPrefix(arg, "versioning-") {
			arg = arg[11:]
			value, ok := folder.Versioning.Params[arg]
			if ok {
				fmt.Println(value)
				return
			}
			die("Versioning property " + c.Args()[1][11:] + " not found")
		}
		switch arg {
		case "directory":
			fmt.Println(folder.RawPath)
		case "type":
			fmt.Println(folder.Type)
		case "permissions":
			fmt.Println(folder.IgnorePerms)
		case "rescan":
			fmt.Println(folder.RescanIntervalS)
		case "versioning":
			if folder.Versioning.Type != "" {
				fmt.Println(folder.Versioning.Type)
			}
		default:
			die("Invalid property: " + c.Args()[1] + "\nAvailable properties: directory, type, permissions, versioning, versioning-<key>")
		}
		return
	}
	die("Folder " + rid + " not found")
}

func foldersSet(c *cli.Context) {
	rid := c.Args()[0]
	arg := strings.ToLower(c.Args()[1])
	val := strings.Join(c.Args()[2:], " ")
	cfg := getConfig(c)
	for i, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		if strings.HasPrefix(arg, "versioning-") {
			cfg.Folders[i].Versioning.Params[arg[11:]] = val
			setConfig(c, cfg)
			return
		}
		switch arg {
		case "directory":
			cfg.Folders[i].RawPath = val
		case "type":
			var t config.FolderType
			if err := t.UnmarshalText([]byte(val)); err != nil {
				die("Invalid folder type: " + err.Error())
			}
			cfg.Folders[i].Type = t
		case "permissions":
			cfg.Folders[i].IgnorePerms = parseBool(val)
		case "rescan":
			cfg.Folders[i].RescanIntervalS = parseInt(val)
		case "versioning":
			cfg.Folders[i].Versioning.Type = val
		default:
			die("Invalid property: " + c.Args()[1] + "\nAvailable properties: directory, master, permissions, versioning, versioning-<key>")
		}
		setConfig(c, cfg)
		return
	}
	die("Folder " + rid + " not found")
}

func foldersUnset(c *cli.Context) {
	rid := c.Args()[0]
	arg := strings.ToLower(c.Args()[1])
	cfg := getConfig(c)
	for i, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		if strings.HasPrefix(arg, "versioning-") {
			arg = arg[11:]
			if _, ok := folder.Versioning.Params[arg]; ok {
				delete(cfg.Folders[i].Versioning.Params, arg)
				setConfig(c, cfg)
				return
			}
			die("Versioning property " + c.Args()[1][11:] + " not found")
		}
		switch arg {
		case "versioning":
			cfg.Folders[i].Versioning.Type = ""
			cfg.Folders[i].Versioning.Params = make(map[string]string)
		default:
			die("Invalid property: " + c.Args()[1] + "\nAvailable properties: versioning, versioning-<key>")
		}
		setConfig(c, cfg)
		return
	}
	die("Folder " + rid + " not found")
}

func foldersDevicesList(c *cli.Context) {
	rid := c.Args()[0]
	cfg := getConfig(c)
	for _, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		for _, device := range folder.Devices {
			fmt.Println(device.DeviceID)
		}
		return
	}
	die("Folder " + rid + " not found")
}

func foldersDevicesAdd(c *cli.Context) {
	rid := c.Args()[0]
	nid := parseDeviceID(c.Args()[1])
	cfg := getConfig(c)
	for i, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		for _, device := range folder.Devices {
			if device.DeviceID == nid {
				die("Device " + c.Args()[1] + " is already part of this folder")
			}
		}
		for _, device := range cfg.Devices {
			if device.DeviceID == nid {
				cfg.Folders[i].Devices = append(folder.Devices, config.FolderDeviceConfiguration{
					DeviceID: device.DeviceID,
				})
				setConfig(c, cfg)
				return
			}
		}
		die("Device " + c.Args()[1] + " not found in device list")
	}
	die("Folder " + rid + " not found")
}

func foldersDevicesRemove(c *cli.Context) {
	rid := c.Args()[0]
	nid := parseDeviceID(c.Args()[1])
	cfg := getConfig(c)
	for ri, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		for ni, device := range folder.Devices {
			if device.DeviceID == nid {
				last := len(folder.Devices) - 1
				cfg.Folders[ri].Devices[ni] = folder.Devices[last]
				cfg.Folders[ri].Devices = cfg.Folders[ri].Devices[:last]
				setConfig(c, cfg)
				return
			}
		}
		die("Device " + c.Args()[1] + " not found")
	}
	die("Folder " + rid + " not found")
}

func foldersDevicesClear(c *cli.Context) {
	rid := c.Args()[0]
	cfg := getConfig(c)
	for i, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		cfg.Folders[i].Devices = []config.FolderDeviceConfiguration{}
		setConfig(c, cfg)
		return
	}
	die("Folder " + rid + " not found")
}
<commit_msg>cmd/stcli: Update for new folder type<commit_after>package main

import (
	"fmt"
	"path/filepath"
	"strings"

	"github.com/AudriusButkevicius/cli"
	"github.com/syncthing/syncthing/lib/config"
)

func init() {
	cliCommands = append(cliCommands, cli.Command{
		Name:     "folders",
		HideHelp: true,
		Usage:    "Folder command group",
		Subcommands: []cli.Command{
			{
				Name:     "list",
				Usage:    "List available folders",
				Requires: &cli.Requires{},
				Action:   foldersList,
			},
			{
				Name:     "add",
				Usage:    "Add a new folder",
				Requires: &cli.Requires{"folder id", "directory"},
				Action:   foldersAdd,
			},
			{
				Name:     "remove",
				Usage:    "Remove an existing folder",
				Requires: &cli.Requires{"folder id"},
				Action:   foldersRemove,
			},
			{
				Name:     "override",
				Usage:    "Override changes from other nodes for a master folder",
				Requires: &cli.Requires{"folder id"},
				Action:   foldersOverride,
			},
			{
				Name:     "get",
				Usage:    "Get a property of a folder",
				Requires: &cli.Requires{"folder id", "property"},
				Action:   foldersGet,
			},
			{
				Name:     "set",
				Usage:    "Set a property of a folder",
				Requires: &cli.Requires{"folder id", "property", "value..."},
				Action:   foldersSet,
			},
			{
				Name:     "unset",
				Usage:    "Unset a property of a folder",
				Requires: &cli.Requires{"folder id", "property"},
				Action:   foldersUnset,
			},
			{
				Name:     "devices",
				Usage:    "Folder devices command group",
				HideHelp: true,
				Subcommands: []cli.Command{
					{
						Name:     "list",
						Usage:    "List of devices which the folder is shared with",
						Requires: &cli.Requires{"folder id"},
						Action:   foldersDevicesList,
					},
					{
						Name:     "add",
						Usage:    "Share a folder with a device",
						Requires: &cli.Requires{"folder id", "device id"},
						Action:   foldersDevicesAdd,
					},
					{
						Name:     "remove",
						Usage:    "Unshare a folder with a device",
						Requires: &cli.Requires{"folder id", "device id"},
						Action:   foldersDevicesRemove,
					},
					{
						Name:     "clear",
						Usage:    "Unshare a folder with all devices",
						Requires: &cli.Requires{"folder id"},
						Action:   foldersDevicesClear,
					},
				},
			},
		},
	})
}

func foldersList(c *cli.Context) {
	cfg := getConfig(c)
	first := true
	writer := newTableWriter()
	for _, folder := range cfg.Folders {
		if !first {
			fmt.Fprintln(writer)
		}
		fmt.Fprintln(writer, "ID:\t", folder.ID, "\t")
		fmt.Fprintln(writer, "Path:\t", folder.RawPath, "\t(directory)")
		fmt.Fprintln(writer, "Folder type:\t", folder.Type, "\t(type)")
		fmt.Fprintln(writer, "Ignore permissions:\t", folder.IgnorePerms, "\t(permissions)")
		fmt.Fprintln(writer, "Rescan interval in seconds:\t", folder.RescanIntervalS, "\t(rescan)")

		if folder.Versioning.Type != "" {
			fmt.Fprintln(writer, "Versioning:\t", folder.Versioning.Type, "\t(versioning)")
			for key, value := range folder.Versioning.Params {
				fmt.Fprintf(writer, "Versioning %s:\t %s \t(versioning-%s)\n", key, value, key)
			}
		}
		first = false
	}
	writer.Flush()
}

func foldersAdd(c *cli.Context) {
	cfg := getConfig(c)
	abs, err := filepath.Abs(c.Args()[1])
	die(err)
	folder := config.FolderConfiguration{
		ID:      c.Args()[0],
		RawPath: filepath.Clean(abs),
	}
	cfg.Folders = append(cfg.Folders, folder)
	setConfig(c, cfg)
}

func foldersRemove(c *cli.Context) {
	cfg := getConfig(c)
	rid := c.Args()[0]
	for i, folder := range cfg.Folders {
		if folder.ID == rid {
			last := len(cfg.Folders) - 1
			cfg.Folders[i] = cfg.Folders[last]
			cfg.Folders = cfg.Folders[:last]
			setConfig(c, cfg)
			return
		}
	}
	die("Folder " + rid + " not found")
}

func foldersOverride(c *cli.Context) {
	cfg := getConfig(c)
	rid := c.Args()[0]
	for _, folder := range cfg.Folders {
		if folder.ID == rid && folder.Type == config.FolderTypeSendOnly {
			response := httpPost(c, "db/override", "")
			if response.StatusCode != 200 {
				err := fmt.Sprint("Failed to override changes\nStatus code: ", response.StatusCode)
				body := string(responseToBArray(response))
				if body != "" {
					err += "\nBody: " + body
				}
				die(err)
			}
			return
		}
	}
	die("Folder " + rid + " not found or folder not master")
}

func foldersGet(c *cli.Context) {
	cfg := getConfig(c)
	rid := c.Args()[0]
	arg := strings.ToLower(c.Args()[1])
	for _, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		if strings.HasPrefix(arg, "versioning-") {
			arg = arg[11:]
			value, ok := folder.Versioning.Params[arg]
			if ok {
				fmt.Println(value)
				return
			}
			die("Versioning property " + c.Args()[1][11:] + " not found")
		}
		switch arg {
		case "directory":
			fmt.Println(folder.RawPath)
		case "type":
			fmt.Println(folder.Type)
		case "permissions":
			fmt.Println(folder.IgnorePerms)
		case "rescan":
			fmt.Println(folder.RescanIntervalS)
		case "versioning":
			if folder.Versioning.Type != "" {
				fmt.Println(folder.Versioning.Type)
			}
		default:
			die("Invalid property: " + c.Args()[1] + "\nAvailable properties: directory, type, permissions, versioning, versioning-<key>")
		}
		return
	}
	die("Folder " + rid + " not found")
}

func foldersSet(c *cli.Context) {
	rid := c.Args()[0]
	arg := strings.ToLower(c.Args()[1])
	val := strings.Join(c.Args()[2:], " ")
	cfg := getConfig(c)
	for i, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		if strings.HasPrefix(arg, "versioning-") {
			cfg.Folders[i].Versioning.Params[arg[11:]] = val
			setConfig(c, cfg)
			return
		}
		switch arg {
		case "directory":
			cfg.Folders[i].RawPath = val
		case "type":
			var t config.FolderType
			if err := t.UnmarshalText([]byte(val)); err != nil {
				die("Invalid folder type: " + err.Error())
			}
			cfg.Folders[i].Type = t
		case "permissions":
			cfg.Folders[i].IgnorePerms = parseBool(val)
		case "rescan":
			cfg.Folders[i].RescanIntervalS = parseInt(val)
		case "versioning":
			cfg.Folders[i].Versioning.Type = val
		default:
			die("Invalid property: " + c.Args()[1] + "\nAvailable properties: directory, master, permissions, versioning, versioning-<key>")
		}
		setConfig(c, cfg)
		return
	}
	die("Folder " + rid + " not found")
}

func foldersUnset(c *cli.Context) {
	rid := c.Args()[0]
	arg := strings.ToLower(c.Args()[1])
	cfg := getConfig(c)
	for i, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		if strings.HasPrefix(arg, "versioning-") {
			arg = arg[11:]
			if _, ok := folder.Versioning.Params[arg]; ok {
				delete(cfg.Folders[i].Versioning.Params, arg)
				setConfig(c, cfg)
				return
			}
			die("Versioning property " + c.Args()[1][11:] + " not found")
		}
		switch arg {
		case "versioning":
			cfg.Folders[i].Versioning.Type = ""
			cfg.Folders[i].Versioning.Params = make(map[string]string)
		default:
			die("Invalid property: " + c.Args()[1] + "\nAvailable properties: versioning, versioning-<key>")
		}
		setConfig(c, cfg)
		return
	}
	die("Folder " + rid + " not found")
}

func foldersDevicesList(c *cli.Context) {
	rid := c.Args()[0]
	cfg := getConfig(c)
	for _, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		for _, device := range folder.Devices {
			fmt.Println(device.DeviceID)
		}
		return
	}
	die("Folder " + rid + " not found")
}

func foldersDevicesAdd(c *cli.Context) {
	rid := c.Args()[0]
	nid := parseDeviceID(c.Args()[1])
	cfg := getConfig(c)
	for i, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		for _, device := range folder.Devices {
			if device.DeviceID == nid {
				die("Device " + c.Args()[1] + " is already part of this folder")
			}
		}
		for _, device := range cfg.Devices {
			if device.DeviceID == nid {
				cfg.Folders[i].Devices = append(folder.Devices, config.FolderDeviceConfiguration{
					DeviceID: device.DeviceID,
				})
				setConfig(c, cfg)
				return
			}
		}
		die("Device " + c.Args()[1] + " not found in device list")
	}
	die("Folder " + rid + " not found")
}

func foldersDevicesRemove(c *cli.Context) {
	rid := c.Args()[0]
	nid := parseDeviceID(c.Args()[1])
	cfg := getConfig(c)
	for ri, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		for ni, device := range folder.Devices {
			if device.DeviceID == nid {
				last := len(folder.Devices) - 1
				cfg.Folders[ri].Devices[ni] = folder.Devices[last]
				cfg.Folders[ri].Devices = cfg.Folders[ri].Devices[:last]
				setConfig(c, cfg)
				return
			}
		}
		die("Device " + c.Args()[1] + " not found")
	}
	die("Folder " + rid + " not found")
}

func foldersDevicesClear(c *cli.Context) {
	rid := c.Args()[0]
	cfg := getConfig(c)
	for i, folder := range cfg.Folders {
		if folder.ID != rid {
			continue
		}
		cfg.Folders[i].Devices = []config.FolderDeviceConfiguration{}
		setConfig(c, cfg)
		return
	}
	die("Folder " + rid + " not found")
}
<|endoftext|>


<commit_before>package main

import(
	"fmt"
	"image"
	"os"
	"github.com/uoregon-libraries/newspaper-jp2-viewer/openjpeg"
)

// Image tile-pulling values
var r image.Rectangle = image.Rect(100, 100, 358, 358)
var width int = 127
var height int = 127

// Verifies that file exists
func checkFile(path string) error {
	_, err := os.Stat(path)
	return err
}

// Wrapper to doVerify which tests file prior to doVerify call, and pushes any
// errors onto the jp2Messages channel
func verifyJP2(path string) {
	if err := checkFile(path); err != nil {
		jp2Messages <- fmt.Sprintf("ERROR reading JP2 file: %s", err)
		return
	}

	jp2Messages <- doVerify(path)
}

// Verifies a JP2 file can have a tile read by Brikker and openjpeg
func doVerify(path string) string {
	err, _ := openjpeg.NewImageTile(path, r, width, height)

	if (err != nil) {
		return fmt.Sprintf("ERROR creating image tile from %#v: %s", path, err)
	}

	return fmt.Sprintf("SUCCESS: %#v", path)
}

func createWorker() {
	for {
		verifyJP2(<-jp2Files)
	}
}
<commit_msg>Fix comments in verifyJP2s<commit_after>package main

import(
	"fmt"
	"image"
	"os"
	"github.com/uoregon-libraries/newspaper-jp2-viewer/openjpeg"
)

// Image tile-pulling values
var r image.Rectangle = image.Rect(100, 100, 358, 358)
var width int = 127
var height int = 127

// Verifies that file exists
func checkFile(path string) error {
	_, err := os.Stat(path)
	return err
}

// Wrapper to doVerify which tests file prior to doVerify call, and pushes any
// errors onto the jp2Messages channel
func verifyJP2(path string) {
	if err := checkFile(path); err != nil {
		jp2Messages <- fmt.Sprintf("ERROR reading JP2 file: %s", err)
		return
	}

	jp2Messages <- doVerify(path)
}

// Verifies that we can read and serve tiles for the given JP2.  This
// effectively determines if the installed openjpeg libs will work.
func doVerify(path string) string {
	err, _ := openjpeg.NewImageTile(path, r, width, height)

	if (err != nil) {
		return fmt.Sprintf("ERROR creating image tile from %#v: %s", path, err)
	}

	return fmt.Sprintf("SUCCESS: %#v", path)
}

func createWorker() {
	for {
		verifyJP2(<-jp2Files)
	}
}
<|endoftext|>


<commit_before>// Copyright 2016-2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"os/exec"
	"testing"
)

// Test reading from the buffer.
// dmesg
func TestDmesg(t *testing.T) {
	out, err := exec.Command("go", "run", "dmesg.go").Output()
	if err != nil {
		t.Fatalf("Error running dmesg: %v", err)
	}
	// Test passes if anything is read.
	if len(out) == 0 {
		t.Fatalf("Nothing read from dmesg")
	}
}
<commit_msg>cmds/dmesg: use testutil instead of self-baked go run.<commit_after>// Copyright 2016-2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"testing"

	"github.com/u-root/u-root/pkg/testutil"
)

func TestDmesg(t *testing.T) {
	cmd := testutil.Command(t)
	out, err := cmd.Output()
	if err != nil || len(out) == 0 {
		t.Fatal(err)
	}
}

func TestMain(m *testing.M) {
	testutil.Run(m, main)
}
<|endoftext|>


<commit_before>package cmd

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"os/exec"
)

func init() {
	// Mute commands.
	addCmd.SetOut(new(bytes.Buffer))
	addCmd.SetErr(new(bytes.Buffer))
	initCmd.SetOut(new(bytes.Buffer))
	initCmd.SetErr(new(bytes.Buffer))
}

// ensureLF converts any \r\n to \n
func ensureLF(content []byte) []byte {
	return bytes.Replace(content, []byte("\r\n"), []byte("\n"), -1)
}

// compareFiles compares the content of files with pathA and pathB.
// If contents are equal, it returns nil.
// If not, it returns which files are not equal
// and diff (if system has diff command) between these files.
func compareFiles(pathA, pathB string) error {
	contentA, err := ioutil.ReadFile(pathA)
	if err != nil {
		return err
	}
	contentB, err := ioutil.ReadFile(pathB)
	if err != nil {
		return err
	}
	if !bytes.Equal(ensureLF(contentA), ensureLF(contentB)) {
		output := new(bytes.Buffer)
		output.WriteString(fmt.Sprintf("%q and %q are not equal!\n\n", pathA, pathB))

		diffPath, err := exec.LookPath("diff")
		if err != nil {
			// Don't execute diff if it can't be found.
			return nil
		}
		diffCmd := exec.Command(diffPath, "-u", pathA, pathB)
		diffCmd.Stdout = output
		diffCmd.Stderr = output

		output.WriteString("$ diff -u " + pathA + " " + pathB + "\n")
		if err := diffCmd.Run(); err != nil {
			output.WriteString("\n" + err.Error())
		}
		return errors.New(output.String())
	}
	return nil
}
<commit_msg>fix(diff): use arg '--strip-trailing-cr' (#949)<commit_after>package cmd

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"os/exec"
)

func init() {
	// Mute commands.
	addCmd.SetOut(new(bytes.Buffer))
	addCmd.SetErr(new(bytes.Buffer))
	initCmd.SetOut(new(bytes.Buffer))
	initCmd.SetErr(new(bytes.Buffer))
}

// ensureLF converts any \r\n to \n
func ensureLF(content []byte) []byte {
	return bytes.Replace(content, []byte("\r\n"), []byte("\n"), -1)
}

// compareFiles compares the content of files with pathA and pathB.
// If contents are equal, it returns nil.
// If not, it returns which files are not equal
// and diff (if system has diff command) between these files.
func compareFiles(pathA, pathB string) error {
	contentA, err := ioutil.ReadFile(pathA)
	if err != nil {
		return err
	}
	contentB, err := ioutil.ReadFile(pathB)
	if err != nil {
		return err
	}
	if !bytes.Equal(ensureLF(contentA), ensureLF(contentB)) {
		output := new(bytes.Buffer)
		output.WriteString(fmt.Sprintf("%q and %q are not equal!\n\n", pathA, pathB))

		diffPath, err := exec.LookPath("diff")
		if err != nil {
			// Don't execute diff if it can't be found.
			return nil
		}
		diffCmd := exec.Command(diffPath, "-u", "--strip-trailing-cr", pathA, pathB)
		diffCmd.Stdout = output
		diffCmd.Stderr = output

		output.WriteString("$ diff -u " + pathA + " " + pathB + "\n")
		if err := diffCmd.Run(); err != nil {
			output.WriteString("\n" + err.Error())
		}
		return errors.New(output.String())
	}
	return nil
}
<|endoftext|>


<commit_before>package codegen

import (
	"fmt"
	"github.com/rhysd/gocaml/gcil"
	"github.com/rhysd/gocaml/typing"
	"llvm.org/llvm/bindings/go/llvm"
)

type blockBuilder struct {
	*moduleBuilder
	registers map[string]llvm.Value
}

func newBlockBuilder(b *moduleBuilder) *blockBuilder {
	return &blockBuilder{b, map[string]llvm.Value{}}
}

func (b *blockBuilder) resolve(ident string) llvm.Value {
	if glob, ok := b.globalTable[ident]; ok {
		return b.builder.CreateLoad(glob, ident)
	}
	if reg, ok := b.registers[ident]; ok {
		return reg
	}
	panic("No value was found for identifier: " + ident)
}

func (b *blockBuilder) typeOf(ident string) typing.Type {
	if t, ok := b.env.Table[ident]; ok {
		for {
			v, ok := t.(*typing.Var)
			if !ok {
				return t
			}
			if v.Ref == nil {
				panic("Empty type variable while searching variable: " + ident)
			}
			t = v.Ref
		}
	}
	if t, ok := b.env.Externals[ident]; ok {
		for {
			v, ok := t.(*typing.Var)
			if !ok {
				return t
			}
			if v.Ref == nil {
				panic("Empty type variable while searching external variable: " + ident)
			}
			t = v.Ref
		}
	}
	panic("Type was not found for ident: " + ident)
}

func (b *blockBuilder) buildVal(ident string, val gcil.Val) llvm.Value {
	switch val := val.(type) {
	case *gcil.Unit:
		return llvm.ConstStruct([]llvm.Value{}, false /*packed*/)
	case *gcil.Bool:
		c := uint64(1)
		if !val.Const {
			c = 0
		}
		return llvm.ConstInt(b.typeBuilder.boolT, c, false /*sign extend*/)
	case *gcil.Int:
		return llvm.ConstInt(b.typeBuilder.intT, uint64(val.Const), true /*sign extend*/)
	case *gcil.Float:
		return llvm.ConstFloat(b.typeBuilder.floatT, val.Const)
	case *gcil.Unary:
		child := b.resolve(val.Child)
		switch val.Op {
		case gcil.NEG:
			return b.builder.CreateNeg(child, "neg")
		case gcil.FNEG:
			return b.builder.CreateFNeg(child, "fneg")
		case gcil.NOT:
			return b.builder.CreateNot(child, "not")
		default:
			panic("unreachable")
		}
	case *gcil.Binary:
		lhs := b.resolve(val.Lhs)
		rhs := b.resolve(val.Rhs)
		switch val.Op {
		case gcil.ADD:
			return b.builder.CreateAdd(lhs, rhs, "add")
		case gcil.SUB:
			return b.builder.CreateSub(lhs, rhs, "sub")
		case gcil.FADD:
			return b.builder.CreateFAdd(lhs, rhs, "fadd")
		case gcil.FSUB:
			return b.builder.CreateFSub(lhs, rhs, "fsub")
		case gcil.FMUL:
			return b.builder.CreateFMul(lhs, rhs, "fmul")
		case gcil.FDIV:
			return b.builder.CreateFDiv(lhs, rhs, "fdiv")
		case gcil.LESS:
			lty := b.typeOf(val.Lhs)
			switch lty.(type) {
			case *typing.Int:
				return b.builder.CreateICmp(llvm.IntSLT /*Signed Less Than*/, lhs, rhs, "less")
			case *typing.Float:
				return b.builder.CreateFCmp(llvm.FloatOLT /*Ordered and Less Than*/, lhs, rhs, "less")
			default:
				panic("Invalid type for '<' operator: " + lty.String())
			}
		case gcil.EQ:
			lty := b.typeOf(val.Lhs)
			switch lty.(type) {
			case *typing.Unit:
				// `() = ()` is always true.
				return llvm.ConstInt(b.typeBuilder.boolT, 1, false /*sign extend*/)
			case *typing.Bool, *typing.Int:
				return b.builder.CreateICmp(llvm.IntEQ, lhs, rhs, "eql")
			case *typing.Float:
				return b.builder.CreateFCmp(llvm.FloatOEQ, lhs, rhs, "eql")
			case *typing.Tuple:
				panic("not implemented yet: comparing tuples")
			case *typing.Array:
				panic("not implemented yet: comparing arrays")
			default:
				panic("unreachable")
			}
		default:
			panic("unreachable")
		}
	case *gcil.Ref:
		reg, ok := b.registers[val.Ident]
		if !ok {
			panic("Value not found for ref: " + val.Ident)
		}
		return reg
	case *gcil.If:
		parent := b.builder.GetInsertBlock().Parent()
		thenBlock := llvm.AddBasicBlock(parent, "if.then")
		elseBlock := llvm.AddBasicBlock(parent, "if.else")
		endBlock := llvm.AddBasicBlock(parent, "if.end")

		ty := b.typeBuilder.convertGCIL(b.typeOf(ident))
		cond := b.resolve(val.Cond)
		b.builder.CreateCondBr(cond, thenBlock, elseBlock)

		b.builder.SetInsertPointAtEnd(thenBlock)
		thenVal := b.build(val.Then)
		b.builder.CreateBr(endBlock)

		b.builder.SetInsertPointAtEnd(elseBlock)
		elseVal := b.build(val.Else)
		b.builder.CreateBr(endBlock)

		b.builder.SetInsertPointAtEnd(endBlock)
		phi := b.builder.CreatePHI(ty, "if.merge")
		phi.AddIncoming([]llvm.Value{thenVal, elseVal}, []llvm.BasicBlock{thenBlock, elseBlock})
		return phi
	case *gcil.Fun:
		panic("unreachable because IR was closure-transformed")
	case *gcil.App:
		argsLen := len(val.Args)
		if val.Kind == gcil.CLOSURE_CALL {
			argsLen++
		}
		argVals := make([]llvm.Value, 0, argsLen)

		if val.Kind == gcil.CLOSURE_CALL {
			// Add pointer to closure captures
			argVals = append(argVals, b.resolve(val.Callee))
		}
		for _, a := range val.Args {
			argVals = append(argVals, b.resolve(a))
		}

		table := b.funcTable
		if val.Kind == gcil.EXTERNAL_CALL {
			table = b.globalTable
		}
		funVal, ok := table[val.Callee]
		if !ok {
			if val.Kind != gcil.CLOSURE_CALL {
				panic("Value for function is not found in table: " + val.Callee)
			}
			// If callee is a function variable and not well-known, we need to fetch the function pointer
			// to call from closure value.
			ptr := b.builder.CreateStructGEP(argVals[0], 0, "")
			funVal = b.builder.CreateLoad(ptr, "funptr")
		}

		// Note:
		// Call inst cannot have a name when the return type is void.
		return b.builder.CreateCall(funVal, argVals, "")
	case *gcil.Tuple:
		// Note:
		// Type of tuple is a pointer to struct. To obtain the value for tuple, we need underlying
		// struct type because 'alloca' instruction returns the pointer to allocated memory.
		ptrTy := b.typeBuilder.convertGCIL(b.typeOf(ident))
		allocTy := ptrTy.ElementType()

		ptr := b.builder.CreateAlloca(allocTy, ident)
		for i, e := range val.Elems {
			v := b.resolve(e)
			p := b.builder.CreateStructGEP(ptr, i, fmt.Sprintf("%s.%d", ident, i))
			b.builder.CreateStore(v, p)
		}
		return ptr
	case *gcil.Array:
		t, ok := b.typeOf(ident).(*typing.Array)
		if !ok {
			panic("Type of array literal is not array")
		}

		ty := b.typeBuilder.convertGCIL(t)
		elemTy := b.typeBuilder.convertGCIL(t.Elem)
		ptr := b.builder.CreateAlloca(ty, ident)

		sizeVal := b.resolve(val.Size)

		// XXX:
		// Arrays are allocated on stack. So returning array value from function
		// now breaks the array value.
		arrVal := b.builder.CreateArrayAlloca(elemTy, sizeVal, "array.ptr")

		arrPtr := b.builder.CreateStructGEP(ptr, 0, "")
		b.builder.CreateStore(arrVal, arrPtr)

		// Copy second argument to all elements of allocated array
		elemVal := b.resolve(val.Elem)
		iterPtr := b.builder.CreateAlloca(b.typeBuilder.intT, "arr.init.iter")
		b.builder.CreateStore(llvm.ConstInt(b.typeBuilder.intT, 0, false), iterPtr)

		parent := b.builder.GetInsertBlock().Parent()
		loopBlock := llvm.AddBasicBlock(parent, "arr.init.setelem")
		endBlock := llvm.AddBasicBlock(parent, "arr.init.end")

		b.builder.CreateBr(loopBlock)
		b.builder.SetInsertPointAtEnd(loopBlock)

		iterVal := b.builder.CreateLoad(iterPtr, "")
		elemPtr := b.builder.CreateInBoundsGEP(arrVal, []llvm.Value{iterVal}, "")
		b.builder.CreateStore(elemVal, elemPtr)
		iterVal = b.builder.CreateAdd(iterVal, llvm.ConstInt(b.typeBuilder.intT, 1, false), "arr.init.inc")
		b.builder.CreateStore(iterVal, iterPtr)
		compVal := b.builder.CreateICmp(llvm.IntEQ, iterVal, sizeVal, "")
		b.builder.CreateCondBr(compVal, endBlock, loopBlock)
		b.builder.SetInsertPointAtEnd(endBlock)

		// Set size value
		sizePtr := b.builder.CreateStructGEP(ptr, 1, "")
		b.builder.CreateStore(sizeVal, sizePtr)

		return ptr
	case *gcil.TplLoad:
		from := b.resolve(val.From)
		p := b.builder.CreateStructGEP(from, val.Index, "")
		return b.builder.CreateLoad(p, "tplload")
	case *gcil.ArrLoad:
		fromVal := b.resolve(val.From)
		idxVal := b.resolve(val.Index)
		arrPtr := b.builder.CreateLoad(b.builder.CreateStructGEP(fromVal, 0, ""), "")
		elemPtr := b.builder.CreateInBoundsGEP(arrPtr, []llvm.Value{idxVal}, "")
		return b.builder.CreateLoad(elemPtr, "arrload")
	case *gcil.ArrStore:
		toVal := b.resolve(val.To)
		idxVal := b.resolve(val.Index)
		rhsVal := b.resolve(val.Rhs)
		arrPtr := b.builder.CreateStructGEP(toVal, 0, "")
		elemPtr := b.builder.CreateInBoundsGEP(arrPtr, []llvm.Value{idxVal}, "")
		return b.builder.CreateStore(rhsVal, elemPtr)
	case *gcil.XRef:
		x, ok := b.globalTable[val.Ident]
		if !ok {
			panic("Value for external value not found: " + val.Ident)
		}
		return b.builder.CreateLoad(x, val.Ident)
	case *gcil.MakeCls:
		closure, ok := b.closures[val.Fun]
		if !ok {
			panic("Closure for function not found: " + val.Fun)
		}
		closureTy := b.typeBuilder.buildCapturesStruct(val.Fun, closure)
		alloca := b.builder.CreateAlloca(closureTy, "")

		// Set function pointer to first field of closure
		funVal, ok := b.funcTable[val.Fun]
		if !ok {
			panic("Value for function not found: " + val.Fun)
		}
		b.builder.CreateStore(funVal, b.builder.CreateStructGEP(alloca, 0, ""))

		// Set captures to rest of struct
		for i, v := range val.Vars {
			ptr := b.builder.CreateStructGEP(alloca, i+1, "")
			freevar := b.resolve(v)
			b.builder.CreateStore(freevar, ptr)
		}

		ptr := b.builder.CreateBitCast(alloca, b.typeBuilder.voidPtrT, fmt.Sprintf("closure.%s", val.Fun))
		return ptr
	case *gcil.NOP:
		panic("unreachable")
	default:
		panic("unreachable")
	}
}

func (b *blockBuilder) buildInsn(insn *gcil.Insn) llvm.Value {
	v := b.buildVal(insn.Ident, insn.Val)
	b.registers[insn.Ident] = v
	return v
}

func (b *blockBuilder) build(block *gcil.Block) llvm.Value {
	i := block.Top.Next
	for {
		v := b.buildInsn(i)
		i = i.Next
		if i.Next == nil {
			return v
		}
	}
}
<commit_msg>implement comparing tuples<commit_after>package codegen

import (
	"fmt"
	"github.com/rhysd/gocaml/gcil"
	"github.com/rhysd/gocaml/typing"
	"llvm.org/llvm/bindings/go/llvm"
)

type blockBuilder struct {
	*moduleBuilder
	registers map[string]llvm.Value
}

func newBlockBuilder(b *moduleBuilder) *blockBuilder {
	return &blockBuilder{b, map[string]llvm.Value{}}
}

func (b *blockBuilder) resolve(ident string) llvm.Value {
	if glob, ok := b.globalTable[ident]; ok {
		return b.builder.CreateLoad(glob, ident)
	}
	if reg, ok := b.registers[ident]; ok {
		return reg
	}
	panic("No value was found for identifier: " + ident)
}

func (b *blockBuilder) typeOf(ident string) typing.Type {
	if t, ok := b.env.Table[ident]; ok {
		for {
			v, ok := t.(*typing.Var)
			if !ok {
				return t
			}
			if v.Ref == nil {
				panic("Empty type variable while searching variable: " + ident)
			}
			t = v.Ref
		}
	}
	if t, ok := b.env.Externals[ident]; ok {
		for {
			v, ok := t.(*typing.Var)
			if !ok {
				return t
			}
			if v.Ref == nil {
				panic("Empty type variable while searching external variable: " + ident)
			}
			t = v.Ref
		}
	}
	panic("Type was not found for ident: " + ident)
}

func (b *blockBuilder) buildEq(ty typing.Type, lhs, rhs llvm.Value) llvm.Value {
	switch ty := ty.(type) {
	case *typing.Unit:
		// `() = ()` is always true.
		return llvm.ConstInt(b.typeBuilder.boolT, 1, false /*sign extend*/)
	case *typing.Bool, *typing.Int:
		return b.builder.CreateICmp(llvm.IntEQ, lhs, rhs, "eql")
	case *typing.Float:
		return b.builder.CreateFCmp(llvm.FloatOEQ, lhs, rhs, "eql")
	case *typing.Tuple:
		cmp := llvm.Value{}
		for i, elemTy := range ty.Elems {
			l := b.builder.CreateLoad(b.builder.CreateStructGEP(lhs, i, "tpl.left"), "")
			r := b.builder.CreateLoad(b.builder.CreateStructGEP(rhs, i, "tpl.right"), "")
			elemCmp := b.buildEq(elemTy, l, r)
			if cmp.C == nil {
				cmp = elemCmp
			} else {
				cmp = b.builder.CreateAnd(cmp, elemCmp, "")
			}
		}
		cmp.SetName("eql.tpl")
		return cmp
	case *typing.Array:
		panic("unreachable")
	default:
		panic("unreachable")
	}
}

func (b *blockBuilder) buildVal(ident string, val gcil.Val) llvm.Value {
	switch val := val.(type) {
	case *gcil.Unit:
		return llvm.ConstStruct([]llvm.Value{}, false /*packed*/)
	case *gcil.Bool:
		c := uint64(1)
		if !val.Const {
			c = 0
		}
		return llvm.ConstInt(b.typeBuilder.boolT, c, false /*sign extend*/)
	case *gcil.Int:
		return llvm.ConstInt(b.typeBuilder.intT, uint64(val.Const), true /*sign extend*/)
	case *gcil.Float:
		return llvm.ConstFloat(b.typeBuilder.floatT, val.Const)
	case *gcil.Unary:
		child := b.resolve(val.Child)
		switch val.Op {
		case gcil.NEG:
			return b.builder.CreateNeg(child, "neg")
		case gcil.FNEG:
			return b.builder.CreateFNeg(child, "fneg")
		case gcil.NOT:
			return b.builder.CreateNot(child, "not")
		default:
			panic("unreachable")
		}
	case *gcil.Binary:
		lhs := b.resolve(val.Lhs)
		rhs := b.resolve(val.Rhs)
		switch val.Op {
		case gcil.ADD:
			return b.builder.CreateAdd(lhs, rhs, "add")
		case gcil.SUB:
			return b.builder.CreateSub(lhs, rhs, "sub")
		case gcil.FADD:
			return b.builder.CreateFAdd(lhs, rhs, "fadd")
		case gcil.FSUB:
			return b.builder.CreateFSub(lhs, rhs, "fsub")
		case gcil.FMUL:
			return b.builder.CreateFMul(lhs, rhs, "fmul")
		case gcil.FDIV:
			return b.builder.CreateFDiv(lhs, rhs, "fdiv")
		case gcil.LESS:
			lty := b.typeOf(val.Lhs)
			switch lty.(type) {
			case *typing.Int:
				return b.builder.CreateICmp(llvm.IntSLT /*Signed Less Than*/, lhs, rhs, "less")
			case *typing.Float:
				return b.builder.CreateFCmp(llvm.FloatOLT /*Ordered and Less Than*/, lhs, rhs, "less")
			default:
				panic("Invalid type for '<' operator: " + lty.String())
			}
		case gcil.EQ:
			return b.buildEq(b.typeOf(val.Lhs), lhs, rhs)
		default:
			panic("unreachable")
		}
	case *gcil.Ref:
		reg, ok := b.registers[val.Ident]
		if !ok {
			panic("Value not found for ref: " + val.Ident)
		}
		return reg
	case *gcil.If:
		parent := b.builder.GetInsertBlock().Parent()
		thenBlock := llvm.AddBasicBlock(parent, "if.then")
		elseBlock := llvm.AddBasicBlock(parent, "if.else")
		endBlock := llvm.AddBasicBlock(parent, "if.end")

		ty := b.typeBuilder.convertGCIL(b.typeOf(ident))
		cond := b.resolve(val.Cond)
		b.builder.CreateCondBr(cond, thenBlock, elseBlock)

		b.builder.SetInsertPointAtEnd(thenBlock)
		thenVal := b.build(val.Then)
		b.builder.CreateBr(endBlock)

		b.builder.SetInsertPointAtEnd(elseBlock)
		elseVal := b.build(val.Else)
		b.builder.CreateBr(endBlock)

		b.builder.SetInsertPointAtEnd(endBlock)
		phi := b.builder.CreatePHI(ty, "if.merge")
		phi.AddIncoming([]llvm.Value{thenVal, elseVal}, []llvm.BasicBlock{thenBlock, elseBlock})
		return phi
	case *gcil.Fun:
		panic("unreachable because IR was closure-transformed")
	case *gcil.App:
		argsLen := len(val.Args)
		if val.Kind == gcil.CLOSURE_CALL {
			argsLen++
		}
		argVals := make([]llvm.Value, 0, argsLen)

		if val.Kind == gcil.CLOSURE_CALL {
			// Add pointer to closure captures
			argVals = append(argVals, b.resolve(val.Callee))
		}
		for _, a := range val.Args {
			argVals = append(argVals, b.resolve(a))
		}

		table := b.funcTable
		if val.Kind == gcil.EXTERNAL_CALL {
			table = b.globalTable
		}
		funVal, ok := table[val.Callee]
		if !ok {
			if val.Kind != gcil.CLOSURE_CALL {
				panic("Value for function is not found in table: " + val.Callee)
			}
			// If callee is a function variable and not well-known, we need to fetch the function pointer
			// to call from closure value.
			ptr := b.builder.CreateStructGEP(argVals[0], 0, "")
			funVal = b.builder.CreateLoad(ptr, "funptr")
		}

		// Note:
		// Call inst cannot have a name when the return type is void.
		return b.builder.CreateCall(funVal, argVals, "")
	case *gcil.Tuple:
		// Note:
		// Type of tuple is a pointer to struct. To obtain the value for tuple, we need underlying
		// struct type because 'alloca' instruction returns the pointer to allocated memory.
		ptrTy := b.typeBuilder.convertGCIL(b.typeOf(ident))
		allocTy := ptrTy.ElementType()

		ptr := b.builder.CreateAlloca(allocTy, ident)
		for i, e := range val.Elems {
			v := b.resolve(e)
			p := b.builder.CreateStructGEP(ptr, i, fmt.Sprintf("%s.%d", ident, i))
			b.builder.CreateStore(v, p)
		}
		return ptr
	case *gcil.Array:
		t, ok := b.typeOf(ident).(*typing.Array)
		if !ok {
			panic("Type of array literal is not array")
		}

		ty := b.typeBuilder.convertGCIL(t)
		elemTy := b.typeBuilder.convertGCIL(t.Elem)
		ptr := b.builder.CreateAlloca(ty, ident)

		sizeVal := b.resolve(val.Size)

		// XXX:
		// Arrays are allocated on stack. So returning array value from function
		// now breaks the array value.
		arrVal := b.builder.CreateArrayAlloca(elemTy, sizeVal, "array.ptr")

		arrPtr := b.builder.CreateStructGEP(ptr, 0, "")
		b.builder.CreateStore(arrVal, arrPtr)

		// Copy second argument to all elements of allocated array
		elemVal := b.resolve(val.Elem)
		iterPtr := b.builder.CreateAlloca(b.typeBuilder.intT, "arr.init.iter")
		b.builder.CreateStore(llvm.ConstInt(b.typeBuilder.intT, 0, false), iterPtr)

		parent := b.builder.GetInsertBlock().Parent()
		loopBlock := llvm.AddBasicBlock(parent, "arr.init.setelem")
		endBlock := llvm.AddBasicBlock(parent, "arr.init.end")

		b.builder.CreateBr(loopBlock)
		b.builder.SetInsertPointAtEnd(loopBlock)

		iterVal := b.builder.CreateLoad(iterPtr, "")
		elemPtr := b.builder.CreateInBoundsGEP(arrVal, []llvm.Value{iterVal}, "")
		b.builder.CreateStore(elemVal, elemPtr)
		iterVal = b.builder.CreateAdd(iterVal, llvm.ConstInt(b.typeBuilder.intT, 1, false), "arr.init.inc")
		b.builder.CreateStore(iterVal, iterPtr)
		compVal := b.builder.CreateICmp(llvm.IntEQ, iterVal, sizeVal, "")
		b.builder.CreateCondBr(compVal, endBlock, loopBlock)
		b.builder.SetInsertPointAtEnd(endBlock)

		// Set size value
		sizePtr := b.builder.CreateStructGEP(ptr, 1, "")
		b.builder.CreateStore(sizeVal, sizePtr)

		return ptr
	case *gcil.TplLoad:
		from := b.resolve(val.From)
		p := b.builder.CreateStructGEP(from, val.Index, "")
		return b.builder.CreateLoad(p, "tplload")
	case *gcil.ArrLoad:
		fromVal := b.resolve(val.From)
		idxVal := b.resolve(val.Index)
		arrPtr := b.builder.CreateLoad(b.builder.CreateStructGEP(fromVal, 0, ""), "")
		elemPtr := b.builder.CreateInBoundsGEP(arrPtr, []llvm.Value{idxVal}, "")
		return b.builder.CreateLoad(elemPtr, "arrload")
	case *gcil.ArrStore:
		toVal := b.resolve(val.To)
		idxVal := b.resolve(val.Index)
		rhsVal := b.resolve(val.Rhs)
		arrPtr := b.builder.CreateStructGEP(toVal, 0, "")
		elemPtr := b.builder.CreateInBoundsGEP(arrPtr, []llvm.Value{idxVal}, "")
		return b.builder.CreateStore(rhsVal, elemPtr)
	case *gcil.XRef:
		x, ok := b.globalTable[val.Ident]
		if !ok {
			panic("Value for external value not found: " + val.Ident)
		}
		return b.builder.CreateLoad(x, val.Ident)
	case *gcil.MakeCls:
		closure, ok := b.closures[val.Fun]
		if !ok {
			panic("Closure for function not found: " + val.Fun)
		}
		closureTy := b.typeBuilder.buildCapturesStruct(val.Fun, closure)
		alloca := b.builder.CreateAlloca(closureTy, "")

		// Set function pointer to first field of closure
		funVal, ok := b.funcTable[val.Fun]
		if !ok {
			panic("Value for function not found: " + val.Fun)
		}
		b.builder.CreateStore(funVal, b.builder.CreateStructGEP(alloca, 0, ""))

		// Set captures to rest of struct
		for i, v := range val.Vars {
			ptr := b.builder.CreateStructGEP(alloca, i+1, "")
			freevar := b.resolve(v)
			b.builder.CreateStore(freevar, ptr)
		}

		ptr := b.builder.CreateBitCast(alloca, b.typeBuilder.voidPtrT, fmt.Sprintf("closure.%s", val.Fun))
		return ptr
	case *gcil.NOP:
		panic("unreachable")
	default:
		panic("unreachable")
	}
}

func (b *blockBuilder) buildInsn(insn *gcil.Insn) llvm.Value {
	v := b.buildVal(insn.Ident, insn.Val)
	b.registers[insn.Ident] = v
	return v
}

func (b *blockBuilder) build(block *gcil.Block) llvm.Value {
	i := block.Top.Next
	for {
		v := b.buildInsn(i)
		i = i.Next
		if i.Next == nil {
			return v
		}
	}
}
<|endoftext|>