<commit_before>// Discordgo - Discord bindings for Go
// Available at https://github.com/bwmarrin/discordgo

// Copyright 2015 Bruce Marriner <bruce@sqls.net>.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains all structures for the discordgo package.  These
// may be moved about later into seperate files but I find it easier to have
// them all located together.

package discordgo

import (
	"encoding/json"
	"net"
	"time"

	"github.com/gorilla/websocket"
)

// A Session represents a connection to the Discord REST API.
// token : The authentication token returned from Discord
// Debug : If set to ture debug logging will be displayed.
type Session struct {
	// General configurable settings.
	Token       string // Authentication token for this session
	Debug       bool   // Debug for printing JSON request/responses
	Cache       int    // number in X to cache some responses
	AutoMention bool   // if set to True, ChannelSendMessage will auto mention <@ID>

	// Settable Callback functions for Websocket Events
	OnEvent                   func(*Session, Event) // should Event be *Event?
	OnReady                   func(*Session, Ready)
	OnTypingStart             func(*Session, TypingStart)
	OnMessageCreate           func(*Session, Message)
	OnMessageUpdate           func(*Session, Message)
	OnMessageDelete           func(*Session, MessageDelete)
	OnMessageAck              func(*Session, MessageAck)
	OnPresenceUpdate          func(*Session, PresenceUpdate)
	OnVoiceStateUpdate        func(*Session, VoiceState)
	OnChannelCreate           func(*Session, Channel)
	OnChannelUpdate           func(*Session, Channel)
	OnChannelDelete           func(*Session, Channel)
	OnGuildCreate             func(*Session, Guild)
	OnGuildUpdate             func(*Session, Guild)
	OnGuildDelete             func(*Session, Guild)
	OnGuildMemberAdd          func(*Session, Member)
	OnGuildMemberRemove       func(*Session, Member)
	OnGuildMemberDelete       func(*Session, Member) // which is it?
	OnGuildMemberUpdate       func(*Session, Member)
	OnGuildRoleCreate         func(*Session, GuildRole)
	OnGuildRoleUpdate         func(*Session, GuildRole)
	OnGuildRoleDelete         func(*Session, GuildRoleDelete)
	OnGuildIntegrationsUpdate func(*Session, GuildIntegrationsUpdate)

	// Exposed but should not be modified by User.
	SessionID  string // from websocket READY packet
	DataReady  bool   // Set to true when Data Websocket is ready
	VoiceReady bool   // Set to true when Voice Websocket is ready
	UDPReady   bool   // Set to true when UDP Connection is ready

	// Other..
	wsConn *websocket.Conn
	//TODO, add bools for like.
	// are we connnected to websocket?
	// have we authenticated to login?
	// lets put all the general session
	// tracking and infos here.. clearly

	// Everything below here is used for Voice testing.
	// This stuff is almost guarenteed to change a lot
	// and is even a bit hackish right now.
	VwsConn    *websocket.Conn // new for voice
	VSessionID string
	VToken     string
	VEndpoint  string
	VGuildID   string
	VChannelID string
	Vop2       VoiceOP2
	UDPConn    *net.UDPConn
}

// A Message stores all data related to a specific Discord message.
type Message struct {
	ID              string       `json:"id"`
	Author          User         `json:"author"`
	Content         string       `json:"content"`
	Attachments     []Attachment `json:"attachments"`
	Tts             bool         `json:"tts"`
	Embeds          []Embed      `json:"embeds"`
	Timestamp       string       `json:"timestamp"`
	MentionEveryone bool         `json:"mention_everyone"`
	EditedTimestamp string       `json:"edited_timestamp"`
	Mentions        []User       `json:"mentions"`
	ChannelID       string       `json:"channel_id"`
}

// An Attachment stores data for message attachments.
type Attachment struct { //TODO figure this out
}

// An Embed stores data for message embeds.
type Embed struct { // TODO figure this out
}

// A VoiceRegion stores data for a specific voice region server.
type VoiceRegion struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	Hostname string `json:"sample_hostname"`
	Port     int    `json:"sample_port"`
}

// A VoiceICE stores data for voice ICE servers.
type VoiceICE struct {
	TTL     string      `json:"ttl"`
	Servers []ICEServer `json:"servers"`
}

// A ICEServer stores data for a specific voice ICE server.
type ICEServer struct {
	URL        string `json:"url"`
	Username   string `json:"username"`
	Credential string `json:"credential"`
}

// A Invite stores all data related to a specific Discord Guild or Channel invite.
type Invite struct {
	MaxAge    int     `json:"max_age"`
	Code      string  `json:"code"`
	Guild     Guild   `json:"guild"`
	Revoked   bool    `json:"revoked"`
	CreatedAt string  `json:"created_at"` // TODO make timestamp
	Temporary bool    `json:"temporary"`
	Uses      int     `json:"uses"`
	MaxUses   int     `json:"max_uses"`
	Inviter   User    `json:"inviter"`
	XkcdPass  bool    `json:"xkcdpass"`
	Channel   Channel `json:"channel"`
}

// A Channel holds all data related to an individual Discord channel.
type Channel struct {
	ID                   string                `json:"id"`
	GuildID              string                `json:"guild_id"`
	Name                 string                `json:"name"`
	Topic                string                `json:"topic"`
	Position             int                   `json:"position"`
	Type                 string                `json:"type"`
	PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites"`
	IsPrivate            bool                  `json:"is_private"`
	LastMessageID        string                `json:"last_message_id"`
	Recipient            User                  `json:"recipient"`
}

// A PermissionOverwrite holds permission overwrite data for a Channel
type PermissionOverwrite struct {
	ID    string `json:"id"`
	Type  string `json:"type"`
	Deny  int    `json:"deny"`
	Allow int    `json:"allow"`
}

// A Guild holds all data related to a specific Discord Guild.  Guilds are also
// sometimes referred to as Servers in the Discord client.
type Guild struct {
	ID             string       `json:"id"`
	Name           string       `json:"name"`
	Icon           string       `json:"icon"`
	Region         string       `json:"region"`
	AfkTimeout     int          `json:"afk_timeout"`
	AfkChannelID   string       `json:"afk_channel_id"`
	EmbedChannelID string       `json:"embed_channel_id"`
	EmbedEnabled   bool         `json:"embed_enabled"`
	OwnerID        string       `json:"owner_id"`
	Large          bool         `json:"large"`     // ??
	JoinedAt       string       `json:"joined_at"` // make this a timestamp
	Roles          []Role       `json:"roles"`
	Members        []Member     `json:"members"`
	Presences      []Presence   `json:"presences"`
	Channels       []Channel    `json:"channels"`
	VoiceStates    []VoiceState `json:"voice_states"`
}

// A Role stores information about Discord guild member roles.
type Role struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Managed     bool   `json:"managed"`
	Color       int    `json:"color"`
	Hoist       bool   `json:"hoist"`
	Position    int    `json:"position"`
	Permissions int    `json:"permissions"`
}

// A VoiceState stores the voice states of Guilds
type VoiceState struct {
	UserID    string `json:"user_id"`
	Suppress  bool   `json:"suppress"`
	SessionID string `json:"session_id"`
	SelfMute  bool   `json:"self_mute"`
	SelfDeaf  bool   `json:"self_deaf"`
	Mute      bool   `json:"mute"`
	Deaf      bool   `json:"deaf"`
	ChannelID string `json:"channel_id"`
}

// A Presence stores the online, offline, or idle and game status of Guild members.
type Presence struct {
	User   User   `json:"user"`
	Status string `json:"status"`
	GameID int    `json:"game_id"`
}

// A Member stores user information for Guild members.
type Member struct {
	GuildID  string   `json:"guild_id"`
	JoinedAt string   `json:"joined_at"`
	Deaf     bool     `json:"deaf"`
	Mute     bool     `json:"mute"`
	User     User     `json:"user"`
	Roles    []string `json:"roles"`
}

// A User stores all data for an individual Discord user.
type User struct {
	ID       string `json:"id"`
	Email    string `json:"email"`
	Username string `json:"username"`
	Avatar   string `json:"Avatar"`
	Verified bool   `json:"verified"`
	//Discriminator int    `json:"discriminator,string"` // TODO: See below
}

// TODO: Research issue.
// Discriminator sometimes comes as a string
// and sometimes it comes as a int.  Weird.
// to avoid errors I've just commented it out
// but it doesn't seem to just kill the whole
// program.  Heartbeat is taken on READY even
// with error and the system continues to read
// it just doesn't seem able to handle this one
// field correctly.  Need to research this more.

// A PrivateChannel stores all data for a specific user private channel.
type PrivateChannel struct {
	ID            string `json:"id"`
	IsPrivate     bool   `json:"is_private"`
	LastMessageID string `json:"last_message_id"`
	Recipient     User   `json:"recipient"`
} // merge with channel?

// A Settings stores data for a specific users Discord client settings.
type Settings struct {
	RenderEmbeds          bool     `json:"render_embeds"`
	InlineEmbedMedia      bool     `json:"inline_embed_media"`
	EnableTtsCommand      bool     `json:"enable_tts_command"`
	MessageDisplayCompact bool     `json:"message_display_compact"`
	Locale                string   `json:"locale"`
	ShowCurrentGame       bool     `json:"show_current_game"`
	Theme                 string   `json:"theme"`
	MutedChannels         []string `json:"muted_channels"`
}

// An Event provides a basic initial struct for all websocket event.
type Event struct {
	Type      string          `json:"t"`
	State     int             `json:"s"`
	Operation int             `json:"o"`
	Direction int             `json:"dir"`
	RawData   json.RawMessage `json:"d"`
}

// A Ready stores all data for the websocket READY event.
type Ready struct {
	Version           int           `json:"v"`
	SessionID         string        `json:"session_id"`
	HeartbeatInterval time.Duration `json:"heartbeat_interval"`
	User              User          `json:"user"`
	ReadState         []ReadState
	PrivateChannels   []PrivateChannel
	Guilds            []Guild
}

// A ReadState stores data on the read state of channels.
type ReadState struct {
	MentionCount  int
	LastMessageID string `json:"last_message_id"`
	ID            string `json:"id"`
}

// A TypingStart stores data for the typing start websocket event.
type TypingStart struct {
	UserID    string `json:"user_id"`
	ChannelID string `json:"channel_id"`
	Timestamp int    `json:"timestamp"`
}

// A PresenceUpdate stores data for the pressence update websocket event.
type PresenceUpdate struct {
	User    User     `json:"user"`
	Status  string   `json:"status"`
	Roles   []string `json:"roles"`
	GuildID string   `json:"guild_id"`
	GameID  int      `json:"game_id"`
}

// A MessageAck stores data for the message ack websocket event.
type MessageAck struct {
	MessageID string `json:"message_id"`
	ChannelID string `json:"channel_id"`
}

// A MessageDelete stores data for the message delete websocket event.
type MessageDelete struct {
	ID        string `json:"id"`
	ChannelID string `json:"channel_id"`
} // so much like MessageAck..

// A GuildIntegrationsUpdate stores data for the guild integrations update
// websocket event.
type GuildIntegrationsUpdate struct {
	GuildID string `json:"guild_id"`
}

// A GuildRole stores data for guild role websocket events.
type GuildRole struct {
	Role    Role   `json:"role"`
	GuildID string `json:"guild_id"`
}

// A GuildRoleDelete stores data for the guild role delete websocket event.
type GuildRoleDelete struct {
	RoleID  string `json:"role_id"`
	GuildID string `json:"guild_id"`
}
<commit_msg>Removed Cache variable from Session, as it's not used currently.<commit_after>// Discordgo - Discord bindings for Go
// Available at https://github.com/bwmarrin/discordgo

// Copyright 2015 Bruce Marriner <bruce@sqls.net>.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains all structures for the discordgo package.  These
// may be moved about later into seperate files but I find it easier to have
// them all located together.

package discordgo

import (
	"encoding/json"
	"net"
	"time"

	"github.com/gorilla/websocket"
)

// A Session represents a connection to the Discord REST API.
// token : The authentication token returned from Discord
// Debug : If set to ture debug logging will be displayed.
type Session struct {
	// General configurable settings.
	Token       string // Authentication token for this session
	Debug       bool   // Debug for printing JSON request/responses
	AutoMention bool   // if set to True, ChannelSendMessage will auto mention <@ID>

	// Settable Callback functions for Websocket Events
	OnEvent                   func(*Session, Event) // should Event be *Event?
	OnReady                   func(*Session, Ready)
	OnTypingStart             func(*Session, TypingStart)
	OnMessageCreate           func(*Session, Message)
	OnMessageUpdate           func(*Session, Message)
	OnMessageDelete           func(*Session, MessageDelete)
	OnMessageAck              func(*Session, MessageAck)
	OnPresenceUpdate          func(*Session, PresenceUpdate)
	OnVoiceStateUpdate        func(*Session, VoiceState)
	OnChannelCreate           func(*Session, Channel)
	OnChannelUpdate           func(*Session, Channel)
	OnChannelDelete           func(*Session, Channel)
	OnGuildCreate             func(*Session, Guild)
	OnGuildUpdate             func(*Session, Guild)
	OnGuildDelete             func(*Session, Guild)
	OnGuildMemberAdd          func(*Session, Member)
	OnGuildMemberRemove       func(*Session, Member)
	OnGuildMemberDelete       func(*Session, Member) // which is it?
	OnGuildMemberUpdate       func(*Session, Member)
	OnGuildRoleCreate         func(*Session, GuildRole)
	OnGuildRoleUpdate         func(*Session, GuildRole)
	OnGuildRoleDelete         func(*Session, GuildRoleDelete)
	OnGuildIntegrationsUpdate func(*Session, GuildIntegrationsUpdate)

	// Exposed but should not be modified by User.
	SessionID  string // from websocket READY packet
	DataReady  bool   // Set to true when Data Websocket is ready
	VoiceReady bool   // Set to true when Voice Websocket is ready
	UDPReady   bool   // Set to true when UDP Connection is ready

	// Other..
	wsConn *websocket.Conn
	//TODO, add bools for like.
	// are we connnected to websocket?
	// have we authenticated to login?
	// lets put all the general session
	// tracking and infos here.. clearly

	// Everything below here is used for Voice testing.
	// This stuff is almost guarenteed to change a lot
	// and is even a bit hackish right now.
	VwsConn    *websocket.Conn // new for voice
	VSessionID string
	VToken     string
	VEndpoint  string
	VGuildID   string
	VChannelID string
	Vop2       VoiceOP2
	UDPConn    *net.UDPConn
}

// A Message stores all data related to a specific Discord message.
type Message struct {
	ID              string       `json:"id"`
	Author          User         `json:"author"`
	Content         string       `json:"content"`
	Attachments     []Attachment `json:"attachments"`
	Tts             bool         `json:"tts"`
	Embeds          []Embed      `json:"embeds"`
	Timestamp       string       `json:"timestamp"`
	MentionEveryone bool         `json:"mention_everyone"`
	EditedTimestamp string       `json:"edited_timestamp"`
	Mentions        []User       `json:"mentions"`
	ChannelID       string       `json:"channel_id"`
}

// An Attachment stores data for message attachments.
type Attachment struct { //TODO figure this out
}

// An Embed stores data for message embeds.
type Embed struct { // TODO figure this out
}

// A VoiceRegion stores data for a specific voice region server.
type VoiceRegion struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	Hostname string `json:"sample_hostname"`
	Port     int    `json:"sample_port"`
}

// A VoiceICE stores data for voice ICE servers.
type VoiceICE struct {
	TTL     string      `json:"ttl"`
	Servers []ICEServer `json:"servers"`
}

// A ICEServer stores data for a specific voice ICE server.
type ICEServer struct {
	URL        string `json:"url"`
	Username   string `json:"username"`
	Credential string `json:"credential"`
}

// A Invite stores all data related to a specific Discord Guild or Channel invite.
type Invite struct {
	MaxAge    int     `json:"max_age"`
	Code      string  `json:"code"`
	Guild     Guild   `json:"guild"`
	Revoked   bool    `json:"revoked"`
	CreatedAt string  `json:"created_at"` // TODO make timestamp
	Temporary bool    `json:"temporary"`
	Uses      int     `json:"uses"`
	MaxUses   int     `json:"max_uses"`
	Inviter   User    `json:"inviter"`
	XkcdPass  bool    `json:"xkcdpass"`
	Channel   Channel `json:"channel"`
}

// A Channel holds all data related to an individual Discord channel.
type Channel struct {
	ID                   string                `json:"id"`
	GuildID              string                `json:"guild_id"`
	Name                 string                `json:"name"`
	Topic                string                `json:"topic"`
	Position             int                   `json:"position"`
	Type                 string                `json:"type"`
	PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites"`
	IsPrivate            bool                  `json:"is_private"`
	LastMessageID        string                `json:"last_message_id"`
	Recipient            User                  `json:"recipient"`
}

// A PermissionOverwrite holds permission overwrite data for a Channel
type PermissionOverwrite struct {
	ID    string `json:"id"`
	Type  string `json:"type"`
	Deny  int    `json:"deny"`
	Allow int    `json:"allow"`
}

// A Guild holds all data related to a specific Discord Guild.  Guilds are also
// sometimes referred to as Servers in the Discord client.
type Guild struct {
	ID             string       `json:"id"`
	Name           string       `json:"name"`
	Icon           string       `json:"icon"`
	Region         string       `json:"region"`
	AfkTimeout     int          `json:"afk_timeout"`
	AfkChannelID   string       `json:"afk_channel_id"`
	EmbedChannelID string       `json:"embed_channel_id"`
	EmbedEnabled   bool         `json:"embed_enabled"`
	OwnerID        string       `json:"owner_id"`
	Large          bool         `json:"large"`     // ??
	JoinedAt       string       `json:"joined_at"` // make this a timestamp
	Roles          []Role       `json:"roles"`
	Members        []Member     `json:"members"`
	Presences      []Presence   `json:"presences"`
	Channels       []Channel    `json:"channels"`
	VoiceStates    []VoiceState `json:"voice_states"`
}

// A Role stores information about Discord guild member roles.
type Role struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Managed     bool   `json:"managed"`
	Color       int    `json:"color"`
	Hoist       bool   `json:"hoist"`
	Position    int    `json:"position"`
	Permissions int    `json:"permissions"`
}

// A VoiceState stores the voice states of Guilds
type VoiceState struct {
	UserID    string `json:"user_id"`
	Suppress  bool   `json:"suppress"`
	SessionID string `json:"session_id"`
	SelfMute  bool   `json:"self_mute"`
	SelfDeaf  bool   `json:"self_deaf"`
	Mute      bool   `json:"mute"`
	Deaf      bool   `json:"deaf"`
	ChannelID string `json:"channel_id"`
}

// A Presence stores the online, offline, or idle and game status of Guild members.
type Presence struct {
	User   User   `json:"user"`
	Status string `json:"status"`
	GameID int    `json:"game_id"`
}

// A Member stores user information for Guild members.
type Member struct {
	GuildID  string   `json:"guild_id"`
	JoinedAt string   `json:"joined_at"`
	Deaf     bool     `json:"deaf"`
	Mute     bool     `json:"mute"`
	User     User     `json:"user"`
	Roles    []string `json:"roles"`
}

// A User stores all data for an individual Discord user.
type User struct {
	ID       string `json:"id"`
	Email    string `json:"email"`
	Username string `json:"username"`
	Avatar   string `json:"Avatar"`
	Verified bool   `json:"verified"`
	//Discriminator int    `json:"discriminator,string"` // TODO: See below
}

// TODO: Research issue.
// Discriminator sometimes comes as a string
// and sometimes it comes as a int.  Weird.
// to avoid errors I've just commented it out
// but it doesn't seem to just kill the whole
// program.  Heartbeat is taken on READY even
// with error and the system continues to read
// it just doesn't seem able to handle this one
// field correctly.  Need to research this more.

// A PrivateChannel stores all data for a specific user private channel.
type PrivateChannel struct {
	ID            string `json:"id"`
	IsPrivate     bool   `json:"is_private"`
	LastMessageID string `json:"last_message_id"`
	Recipient     User   `json:"recipient"`
} // merge with channel?

// A Settings stores data for a specific users Discord client settings.
type Settings struct {
	RenderEmbeds          bool     `json:"render_embeds"`
	InlineEmbedMedia      bool     `json:"inline_embed_media"`
	EnableTtsCommand      bool     `json:"enable_tts_command"`
	MessageDisplayCompact bool     `json:"message_display_compact"`
	Locale                string   `json:"locale"`
	ShowCurrentGame       bool     `json:"show_current_game"`
	Theme                 string   `json:"theme"`
	MutedChannels         []string `json:"muted_channels"`
}

// An Event provides a basic initial struct for all websocket event.
type Event struct {
	Type      string          `json:"t"`
	State     int             `json:"s"`
	Operation int             `json:"o"`
	Direction int             `json:"dir"`
	RawData   json.RawMessage `json:"d"`
}

// A Ready stores all data for the websocket READY event.
type Ready struct {
	Version           int           `json:"v"`
	SessionID         string        `json:"session_id"`
	HeartbeatInterval time.Duration `json:"heartbeat_interval"`
	User              User          `json:"user"`
	ReadState         []ReadState
	PrivateChannels   []PrivateChannel
	Guilds            []Guild
}

// A ReadState stores data on the read state of channels.
type ReadState struct {
	MentionCount  int
	LastMessageID string `json:"last_message_id"`
	ID            string `json:"id"`
}

// A TypingStart stores data for the typing start websocket event.
type TypingStart struct {
	UserID    string `json:"user_id"`
	ChannelID string `json:"channel_id"`
	Timestamp int    `json:"timestamp"`
}

// A PresenceUpdate stores data for the pressence update websocket event.
type PresenceUpdate struct {
	User    User     `json:"user"`
	Status  string   `json:"status"`
	Roles   []string `json:"roles"`
	GuildID string   `json:"guild_id"`
	GameID  int      `json:"game_id"`
}

// A MessageAck stores data for the message ack websocket event.
type MessageAck struct {
	MessageID string `json:"message_id"`
	ChannelID string `json:"channel_id"`
}

// A MessageDelete stores data for the message delete websocket event.
type MessageDelete struct {
	ID        string `json:"id"`
	ChannelID string `json:"channel_id"`
} // so much like MessageAck..

// A GuildIntegrationsUpdate stores data for the guild integrations update
// websocket event.
type GuildIntegrationsUpdate struct {
	GuildID string `json:"guild_id"`
}

// A GuildRole stores data for guild role websocket events.
type GuildRole struct {
	Role    Role   `json:"role"`
	GuildID string `json:"guild_id"`
}

// A GuildRoleDelete stores data for the guild role delete websocket event.
type GuildRoleDelete struct {
	RoleID  string `json:"role_id"`
	GuildID string `json:"guild_id"`
}
<|endoftext|>


<commit_before>// Copyright 2017 Tomas Machalek <tomas.machalek@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package vertigo

import (
	"fmt"
	"log"
)

// -------------------------------------------------------

type structAttrs struct {
	elms map[string]*Structure
}

func (sa *structAttrs) Begin(v *Structure) error {
	_, ok := sa.elms[v.Name]
	fmt.Println("OK ? ", ok)
	if ok {
		return fmt.Errorf("Recursive structures not supported (element %s)", v.Name)
	}
	sa.elms[v.Name] = v
	return nil
}

func (sa *structAttrs) End(name string) (*Structure, error) {
	tmp, ok := sa.elms[name]
	if !ok {
		return nil, fmt.Errorf("Cannot close unopened structure %s", name)
	}
	delete(sa.elms, name)
	return tmp, nil
}

func (sa *structAttrs) GetAttrs() map[string]string {
	ans := make(map[string]string)
	for k, v := range sa.elms {
		for k2, v2 := range v.Attrs {
			ans[k+"."+k2] = v2
		}
	}
	return ans
}

func (sa *structAttrs) Size() int {
	return len(sa.elms)
}

func newStructAttrs() *structAttrs {
	return &structAttrs{elms: make(map[string]*Structure)}
}

// -------------------------------------------------------

// nilStructAttrs can be used e.g. in case user is not
// interested in attaching complete structural attr. information
// to each token and wants to use a custom struct. attr processing
// instead. In such case a significant amount of memory can be
// saved.
type nilStructAttrs struct{}

func (nsa *nilStructAttrs) Begin(v *Structure) error {
	return nil
}

func (nsa *nilStructAttrs) End(name string) (*Structure, error) {
	return &Structure{Name: name}, nil
}

func (nsa *nilStructAttrs) GetAttrs() map[string]string {
	return make(map[string]string)
}

func (nsa *nilStructAttrs) Size() int {
	return 0
}

func newNilStructAttrs() *nilStructAttrs {
	log.Print("WARNING: using nil structattr accumulator")
	return &nilStructAttrs{}
}
<commit_msg>Remove debug message<commit_after>// Copyright 2017 Tomas Machalek <tomas.machalek@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package vertigo

import (
	"fmt"
	"log"
)

// -------------------------------------------------------

type structAttrs struct {
	elms map[string]*Structure
}

func (sa *structAttrs) Begin(v *Structure) error {
	_, ok := sa.elms[v.Name]
	if ok {
		return fmt.Errorf("Recursive structures not supported (element %s)", v.Name)
	}
	sa.elms[v.Name] = v
	return nil
}

func (sa *structAttrs) End(name string) (*Structure, error) {
	tmp, ok := sa.elms[name]
	if !ok {
		return nil, fmt.Errorf("Cannot close unopened structure %s", name)
	}
	delete(sa.elms, name)
	return tmp, nil
}

func (sa *structAttrs) GetAttrs() map[string]string {
	ans := make(map[string]string)
	for k, v := range sa.elms {
		for k2, v2 := range v.Attrs {
			ans[k+"."+k2] = v2
		}
	}
	return ans
}

func (sa *structAttrs) Size() int {
	return len(sa.elms)
}

func newStructAttrs() *structAttrs {
	return &structAttrs{elms: make(map[string]*Structure)}
}

// -------------------------------------------------------

// nilStructAttrs can be used e.g. in case user is not
// interested in attaching complete structural attr. information
// to each token and wants to use a custom struct. attr processing
// instead. In such case a significant amount of memory can be
// saved.
type nilStructAttrs struct{}

func (nsa *nilStructAttrs) Begin(v *Structure) error {
	return nil
}

func (nsa *nilStructAttrs) End(name string) (*Structure, error) {
	return &Structure{Name: name}, nil
}

func (nsa *nilStructAttrs) GetAttrs() map[string]string {
	return make(map[string]string)
}

func (nsa *nilStructAttrs) Size() int {
	return 0
}

func newNilStructAttrs() *nilStructAttrs {
	log.Print("WARNING: using nil structattr accumulator")
	return &nilStructAttrs{}
}
<|endoftext|>


<commit_before>package parser

type ServerHandshakePacket struct {
}

func NewServerHandshakePacket(b []byte) (*ServerHandshakePacket, error) {
	// TODO:
	return &ServerHandshakePacket{}, nil
}

type ClientHandshakePacket struct {
}

func NewClientHandshakePacket(b []byte) (*ClientHandshakePacket, error) {
	// TODO:
	return &ClientHandshakePacket{}, nil
}
<commit_msg>impl. handshake packet parser<commit_after>package parser

import (
	"bufio"
	"bytes"
	"encoding/binary"
)

type ServerHandshakePacket struct {
	ProtocolVersion  uint8
	ServerVersion    string
	ThreadID         uint32
	ScrambleBuffer   uint64
	Filter           uint8
	ServerCapability uint16
	ServerLanguage   uint8
	ServerStatus     uint16
}

func NewServerHandshakePacket(b []byte) (*ServerHandshakePacket, error) {
	var (
		err error
		pkt = &ServerHandshakePacket{}
		r   = bufio.NewReader(bytes.NewReader(b))
	)
	pkt.ProtocolVersion, err = r.ReadByte()
	if err != nil {
		return nil, err
	}
	pkt.ServerVersion, err = r.ReadString(0x00)
	if err != nil {
		return nil, err
	}
	err = binary.Read(r, binary.LittleEndian, &pkt.ThreadID)
	if err != nil {
		return nil, err
	}
	err = binary.Read(r, binary.LittleEndian, &pkt.ScrambleBuffer)
	if err != nil {
		return nil, err
	}
	pkt.Filter, err = r.ReadByte()
	if err != nil {
		return nil, err
	}
	err = binary.Read(r, binary.LittleEndian, &pkt.ServerCapability)
	if err != nil {
		return nil, err
	}
	pkt.ServerLanguage, err = r.ReadByte()
	if err != nil {
		return nil, err
	}
	err = binary.Read(r, binary.LittleEndian, &pkt.ServerStatus)
	if err != nil {
		return nil, err
	}
	// TODO:
	return pkt, nil
}

type ClientHandshakePacket struct {
}

func NewClientHandshakePacket(b []byte) (*ClientHandshakePacket, error) {
	// TODO:
	return &ClientHandshakePacket{}, nil
}
<|endoftext|>


<commit_before>// DO NOT EDIT: Auto generated

package demoinfocs

import (
	"time"

	common "github.com/markus-wa/demoinfocs-golang/common"
	st "github.com/markus-wa/demoinfocs-golang/sendtables"
	dp "github.com/markus-wa/godispatch"
)

// IParser is an auto-generated interface for Parser, intended to be used when mockability is needed.
// Parser can parse a CS:GO demo.
// Creating a new instance is done via NewParser().
//
// To start off you may use Parser.ParseHeader() to parse the demo header
// (this can be skipped and will be done automatically if necessary).
// Further, Parser.ParseNextFrame() and Parser.ParseToEnd() can be used to parse the demo.
//
// Use Parser.RegisterEventHandler() to receive notifications about events.
//
// Example (without error handling):
//
// 	f, _ := os.Open("/path/to/demo.dem")
// 	p := dem.NewParser(f)
// 	header := p.ParseHeader()
// 	fmt.Println("Map:", header.MapName)
// 	p.RegisterEventHandler(func(e events.BombExplode) {
// 		fmt.Printf(e.Site, "went BOOM!")
// 	})
// 	p.ParseToEnd()
//
// Prints out '{A/B} site went BOOM!' when a bomb explodes.
type IParser interface {
	// ServerClasses returns the server-classes of this demo.
	// These are available after events.DataTablesParsed has been fired.
	ServerClasses() st.ServerClasses
	// Header returns the DemoHeader which contains the demo's metadata.
	// Only possible after ParserHeader() has been called.
	Header() common.DemoHeader
	// GameState returns the current game-state.
	// It contains most of the relevant information about the game such as players, teams, scores, grenades etc.
	GameState() IGameState
	// CurrentFrame return the number of the current frame, aka. 'demo-tick' (Since demos often have a different tick-rate than the game).
	// Starts with frame 0, should go up to DemoHeader.PlaybackFrames but might not be the case (usually it's just close to it).
	CurrentFrame() int
	// CurrentTime returns the time elapsed since the start of the demo
	CurrentTime() time.Duration
	// Progress returns the parsing progress from 0 to 1.
	// Where 0 means nothing has been parsed yet and 1 means the demo has been parsed to the end.
	//
	// Might not be 100% correct since it's just based on the reported tick count of the header.
	Progress() float32
	/*
	   RegisterEventHandler registers a handler for game events.

	   The handler must be of type func(<EventType>) where EventType is the kind of event to be handled.
	   To catch all events func(interface{}) can be used.

	   Example:

	   	parser.RegisterEventHandler(func(e events.WeaponFired) {
	   		fmt.Printf("%s fired his %s\n", e.Shooter.Name, e.Weapon.Weapon)
	   	})

	   Parameter handler has to be of type interface{} because lolnogenerics.

	   Returns a identifier with which the handler can be removed via UnregisterEventHandler().
	*/
	RegisterEventHandler(handler interface{}) dp.HandlerIdentifier
	// UnregisterEventHandler removes a game event handler via identifier.
	//
	// The identifier is returned at registration by RegisterEventHandler().
	UnregisterEventHandler(identifier dp.HandlerIdentifier)
	/*
	   RegisterNetMessageHandler registers a handler for net-messages.

	   The handler must be of type func(*<MessageType>) where MessageType is the kind of net-message to be handled.

	   Returns a identifier with which the handler can be removed via UnregisterNetMessageHandler().

	   See also: RegisterEventHandler()
	*/
	RegisterNetMessageHandler(handler interface{}) dp.HandlerIdentifier
	// UnregisterNetMessageHandler removes a net-message handler via identifier.
	//
	// The identifier is returned at registration by RegisterNetMessageHandler().
	UnregisterNetMessageHandler(identifier dp.HandlerIdentifier)
	// ParseHeader attempts to parse the header of the demo and returns it.
	// If not done manually this will be called by Parser.ParseNextFrame() or Parser.ParseToEnd().
	//
	// Returns ErrInvalidFileType if the filestamp (first 8 bytes) doesn't match HL2DEMO.
	ParseHeader() (common.DemoHeader, error)
	// ParseToEnd attempts to parse the demo until the end.
	// Aborts and returns ErrCancelled if Cancel() is called before the end.
	//
	// See also: ParseNextFrame() for other possible errors.
	ParseToEnd() (err error)
	// Cancel aborts ParseToEnd().
	// All information that was already read up to this point may still be used (and new events may still be sent out).
	Cancel()
	/*
	   ParseNextFrame attempts to parse the next frame / demo-tick (not ingame tick).

	   Returns true unless the demo command 'stop' or an error was encountered.

	   May return ErrUnexpectedEndOfDemo for incomplete / corrupt demos.
	   May panic if the demo is corrupt in some way.

	   See also: ParseToEnd() for parsing the complete demo in one go (faster).
	*/
	ParseNextFrame() (moreFrames bool, err error)
}
<commit_msg>Parser: update imports in generated interface code<commit_after>// DO NOT EDIT: Auto generated

package demoinfocs

import (
	"time"

	"github.com/markus-wa/demoinfocs-golang/common"
	st "github.com/markus-wa/demoinfocs-golang/sendtables"
	dp "github.com/markus-wa/godispatch"
)

// IParser is an auto-generated interface for Parser, intended to be used when mockability is needed.
// Parser can parse a CS:GO demo.
// Creating a new instance is done via NewParser().
//
// To start off you may use Parser.ParseHeader() to parse the demo header
// (this can be skipped and will be done automatically if necessary).
// Further, Parser.ParseNextFrame() and Parser.ParseToEnd() can be used to parse the demo.
//
// Use Parser.RegisterEventHandler() to receive notifications about events.
//
// Example (without error handling):
//
// 	f, _ := os.Open("/path/to/demo.dem")
// 	p := dem.NewParser(f)
// 	header := p.ParseHeader()
// 	fmt.Println("Map:", header.MapName)
// 	p.RegisterEventHandler(func(e events.BombExplode) {
// 		fmt.Printf(e.Site, "went BOOM!")
// 	})
// 	p.ParseToEnd()
//
// Prints out '{A/B} site went BOOM!' when a bomb explodes.
type IParser interface {
	// ServerClasses returns the server-classes of this demo.
	// These are available after events.DataTablesParsed has been fired.
	ServerClasses() st.ServerClasses
	// Header returns the DemoHeader which contains the demo's metadata.
	// Only possible after ParserHeader() has been called.
	Header() common.DemoHeader
	// GameState returns the current game-state.
	// It contains most of the relevant information about the game such as players, teams, scores, grenades etc.
	GameState() IGameState
	// CurrentFrame return the number of the current frame, aka. 'demo-tick' (Since demos often have a different tick-rate than the game).
	// Starts with frame 0, should go up to DemoHeader.PlaybackFrames but might not be the case (usually it's just close to it).
	CurrentFrame() int
	// CurrentTime returns the time elapsed since the start of the demo
	CurrentTime() time.Duration
	// Progress returns the parsing progress from 0 to 1.
	// Where 0 means nothing has been parsed yet and 1 means the demo has been parsed to the end.
	//
	// Might not be 100% correct since it's just based on the reported tick count of the header.
	Progress() float32
	/*
	   RegisterEventHandler registers a handler for game events.

	   The handler must be of type func(<EventType>) where EventType is the kind of event to be handled.
	   To catch all events func(interface{}) can be used.

	   Example:

	   	parser.RegisterEventHandler(func(e events.WeaponFired) {
	   		fmt.Printf("%s fired his %s\n", e.Shooter.Name, e.Weapon.Weapon)
	   	})

	   Parameter handler has to be of type interface{} because lolnogenerics.

	   Returns a identifier with which the handler can be removed via UnregisterEventHandler().
	*/
	RegisterEventHandler(handler interface{}) dp.HandlerIdentifier
	// UnregisterEventHandler removes a game event handler via identifier.
	//
	// The identifier is returned at registration by RegisterEventHandler().
	UnregisterEventHandler(identifier dp.HandlerIdentifier)
	/*
	   RegisterNetMessageHandler registers a handler for net-messages.

	   The handler must be of type func(*<MessageType>) where MessageType is the kind of net-message to be handled.

	   Returns a identifier with which the handler can be removed via UnregisterNetMessageHandler().

	   See also: RegisterEventHandler()
	*/
	RegisterNetMessageHandler(handler interface{}) dp.HandlerIdentifier
	// UnregisterNetMessageHandler removes a net-message handler via identifier.
	//
	// The identifier is returned at registration by RegisterNetMessageHandler().
	UnregisterNetMessageHandler(identifier dp.HandlerIdentifier)
	// ParseHeader attempts to parse the header of the demo and returns it.
	// If not done manually this will be called by Parser.ParseNextFrame() or Parser.ParseToEnd().
	//
	// Returns ErrInvalidFileType if the filestamp (first 8 bytes) doesn't match HL2DEMO.
	ParseHeader() (common.DemoHeader, error)
	// ParseToEnd attempts to parse the demo until the end.
	// Aborts and returns ErrCancelled if Cancel() is called before the end.
	//
	// See also: ParseNextFrame() for other possible errors.
	ParseToEnd() (err error)
	// Cancel aborts ParseToEnd().
	// All information that was already read up to this point may still be used (and new events may still be sent out).
	Cancel()
	/*
	   ParseNextFrame attempts to parse the next frame / demo-tick (not ingame tick).

	   Returns true unless the demo command 'stop' or an error was encountered.

	   May return ErrUnexpectedEndOfDemo for incomplete / corrupt demos.
	   May panic if the demo is corrupt in some way.

	   See also: ParseToEnd() for parsing the complete demo in one go (faster).
	*/
	ParseNextFrame() (moreFrames bool, err error)
}
<|endoftext|>


<commit_before>// Main file

package webhook

import (
    "appengine"
    "appengine/datastore"
    "appengine/user"
    "encoding/json"
    "fmt"
    "github.com/gorilla/mux"
    "html/template"
    "net/http"
    "services"
    "time"
    "strings"
)

var redirectTmpl = template.Must(
    template.ParseFiles("templates/callback.html"))

// Initialize Appengine.
// Only routes, that's it!
func init() {
    route := mux.NewRouter()
    route.HandleFunc("/", root)
    route.HandleFunc("/login", login)
    route.HandleFunc("/cb", callback)
    route.HandleFunc("/connect", connect)
    route.HandleFunc("/created.json", createdJson)
    route.HandleFunc("/w{handler}", hooks)
    route.HandleFunc("/redirect", redirect)
    route.HandleFunc("/save", save)
    route.HandleFunc("/telegram/{telegramToken}", telegramWebhook)
    route.HandleFunc("/trello/{type}/{boardid}", trelloList)
    http.Handle("/", route)
}

// Return list of created webhooks (/created.json)
func createdJson(writer http.ResponseWriter, request *http.Request) {
    context := appengine.NewContext(request)
    appUser := user.Current(context)
    webhooks := getWebhooks(context, appUser.Email)
    list, _ := json.Marshal(webhooks)
    writer.Header().Set("Content-Type", "application/json")
    fmt.Fprintf(writer, string(list))
}

// Redirect use to get trello service approval. (/connect)
func login(writer http.ResponseWriter, request *http.Request) {
    http.Redirect(writer, request, "/", http.StatusFound)
}

// Root handler (/), show for to create new and list of created hooks.
func root(writer http.ResponseWriter, request *http.Request) {
    context := appengine.NewContext(request)
    appUser := user.Current(context)
    if appUser != nil {
        listTmpl := template.Must(
            template.ParseFiles("templates/base.html", "templates/list.html"))
        url, _ := user.LogoutURL(context, "/")
        data := struct {
            AccessToken string
            Logout      string
        }{getAccessToken(context, appUser.Email), url}
        if err := listTmpl.Execute(writer, data); err != nil {
            http.Error(writer, err.Error(), http.StatusInternalServerError)
        }
    } else {
        homeTmpl := template.Must(template.ParseFiles("templates/index.html"))
        homeTmpl.Execute(writer, nil)
    }
}

// Redirect use to get trello service approval. (/connect)
func connect(writer http.ResponseWriter, request *http.Request) {
    http.Redirect(writer, request, services.GetAuthorizeUrl(), http.StatusFound)
}

// Once approval from service is done, read the token, make post request
// to callback handler (/cb) to save token.
func redirect(writer http.ResponseWriter, request *http.Request) {
    if err := redirectTmpl.Execute(writer, nil); err != nil {
        http.Error(writer, err.Error(), http.StatusInternalServerError)
    }
}

// Callback with token in post payload.
func callback(writer http.ResponseWriter, request *http.Request) {
    context := appengine.NewContext(request)
    appUser := user.Current(context)
    accessToken := AccessTokens{
        Email:       appUser.Email,
        AccessToken: request.FormValue("token"),
    }
    key := datastore.NewIncompleteKey(
        context, "AccessTokens", accessTokenKey(context, appUser.Email))
    _, err := datastore.Put(context, key, &accessToken)
    if err != nil {
        http.Error(writer, err.Error(), http.StatusInternalServerError)
        return
    }
    http.Redirect(writer, request, "/", http.StatusFound)
}

// Get list of trello boards or lists.
func trelloList(writer http.ResponseWriter, request *http.Request) {
    vars := mux.Vars(request)
    context := appengine.NewContext(request)
    appUser := user.Current(context)
    writer.Header().Set("Content-Type", "application/json")
    accessToken := getAccessToken(context, appUser.Email)
    if vars["type"] == "lists" {
        fmt.Fprintf(
            writer, services.GetBoardLists(
                context, vars["boardid"], accessToken))
        return
    }
    fmt.Fprintf(writer, services.GetBoards(context, accessToken))
}

// Save new hook from web.
func save(writer http.ResponseWriter, request *http.Request) {
    context := appengine.NewContext(request)
    appUser := user.Current(context)
    response := Response{
        Success: true,
        Reason:  "",
    }
    handler := "w" + services.GetAlphaNumberic(7)
    webhook := Webhook{
        User:    appUser.Email,
        Handler: handler,
        Date:    time.Now(),
        Count:   0,
    }
    if request.FormValue("service") == "trello" {
        webhook.Type = "Trello"
        webhook.BoardId = request.FormValue("board_id")
        webhook.BoardName = request.FormValue("board_name")
        webhook.ListId = request.FormValue("list_id")
        webhook.ListName = request.FormValue("list_name")
        services.PushToTrello(
            context, webhook.ListId,
            getAccessToken(context, webhook.User), "You are connected!", "")
    } else if request.FormValue("service") == "telegram" {
        webhook.Type = "Telegram"
        webhook.TeleChatId, webhook.TeleChatName = services.GetChatIdFromCode(
            context, request.FormValue("tele_code"))
        if webhook.TeleChatId == 0 {
            response.Success = false
            response.Reason = "Invalid code."
        } else {
            services.SendTeleMessage(
                context, "You are connected!", webhook.TeleChatId)
        }
    } else if request.FormValue("service") == "pushover" {
        webhook.Type = "Pushover"
        webhook.POUserKey = request.FormValue("po_userkey")
        status := services.SendPushoverMessage(
            context, "You are connected!", webhook.POUserKey)
        if status == 0 {
            response.Success = false
            response.Reason = "Invalid key."
        }
    } else if request.FormValue("service") == "hipchat" {
        webhook.Type = "Hipchat"
        webhook.HCToken = request.FormValue("hc_token")
        webhook.HCRoomId = request.FormValue("hc_roomid")
        status := services.SendHipchatMessage(
            context, "You are connected!", webhook.HCRoomId,
            webhook.HCToken, "green")
        if !status {
            response.Success = false
            response.Reason = "Invalid room id or token."
        }
    }
    if response.Success {
        key := datastore.NewIncompleteKey(
            context, "Webhook", webhookKey(context, handler))
        _, err := datastore.Put(context, key, &webhook)
        if err != nil {
            context.Infof("%v", err.Error())
            return
        }
        response.Handler = handler
    }
    writer.Header().Set("Content-Type", "application/json")
    resp, _ := json.Marshal(response)
    fmt.Fprintf(writer, string(resp))
}

// Telegram webhook
func telegramWebhook(writer http.ResponseWriter, request *http.Request) {
    vars := mux.Vars(request)
    context := appengine.NewContext(request)
    decoder := json.NewDecoder(request.Body)
    fmt.Fprintf(
        writer, services.Telegram(context, decoder, vars["telegramToken"]))
}

// Actual webhook handler, receive events and post it to connected services.
func hooks(writer http.ResponseWriter, request *http.Request) {
    vars := mux.Vars(request)
    handler := "w" + vars["handler"]
    context := appengine.NewContext(request)
    webhook := getWebhookFromHandler(context, handler)
    if webhook != nil {
        event, desc := services.GetEventData(request)
        context.Infof("%s: %s \n %s", webhook.Type, event, desc)
        if event != "" {
            if webhook.Type == "Trello" {
                services.PushToTrello(
                    context, webhook.ListId,
                    getAccessToken(context, webhook.User), event, desc)
            } else if webhook.Type == "Telegram" {
                services.SendTeleMessage(
                    context, event+"\n"+desc, webhook.TeleChatId)
            } else if webhook.Type == "Pushover" {
                services.SendPushoverMessage(
                    context, event+"\n"+desc, webhook.POUserKey)
            } else if webhook.Type == "Hipchat" {
                color := "red"
                if strings.Index(event, " success ") > -1 {
                    color = "green"
                }
                services.SendHipchatMessage(
                    context, event+"\n"+desc, webhook.HCRoomId,
                    webhook.HCToken, color)
            }
        }
        fmt.Fprintf(writer, "OK")
    }
}
<commit_msg>reorder<commit_after>// Main file

package webhook

import (
    "appengine"
    "appengine/datastore"
    "appengine/user"
    "encoding/json"
    "fmt"
    "github.com/gorilla/mux"
    "html/template"
    "net/http"
    "services"
    "strings"
    "time"
)

var redirectTmpl = template.Must(
    template.ParseFiles("templates/callback.html"))

// Initialize Appengine.
// Only routes, that's it!
func init() {
    route := mux.NewRouter()
    route.HandleFunc("/", root)
    route.HandleFunc("/login", login)
    route.HandleFunc("/cb", callback)
    route.HandleFunc("/connect", connect)
    route.HandleFunc("/created.json", createdJson)
    route.HandleFunc("/w{handler}", hooks)
    route.HandleFunc("/redirect", redirect)
    route.HandleFunc("/save", save)
    route.HandleFunc("/telegram/{telegramToken}", telegramWebhook)
    route.HandleFunc("/trello/{type}/{boardid}", trelloList)
    http.Handle("/", route)
}

// Return list of created webhooks (/created.json)
func createdJson(writer http.ResponseWriter, request *http.Request) {
    context := appengine.NewContext(request)
    appUser := user.Current(context)
    webhooks := getWebhooks(context, appUser.Email)
    list, _ := json.Marshal(webhooks)
    writer.Header().Set("Content-Type", "application/json")
    fmt.Fprintf(writer, string(list))
}

// Redirect use to get trello service approval. (/connect)
func login(writer http.ResponseWriter, request *http.Request) {
    http.Redirect(writer, request, "/", http.StatusFound)
}

// Root handler (/), show for to create new and list of created hooks.
func root(writer http.ResponseWriter, request *http.Request) {
    context := appengine.NewContext(request)
    appUser := user.Current(context)
    if appUser != nil {
        listTmpl := template.Must(
            template.ParseFiles("templates/base.html", "templates/list.html"))
        url, _ := user.LogoutURL(context, "/")
        data := struct {
            AccessToken string
            Logout      string
        }{getAccessToken(context, appUser.Email), url}
        if err := listTmpl.Execute(writer, data); err != nil {
            http.Error(writer, err.Error(), http.StatusInternalServerError)
        }
    } else {
        homeTmpl := template.Must(template.ParseFiles("templates/index.html"))
        homeTmpl.Execute(writer, nil)
    }
}

// Redirect use to get trello service approval. (/connect)
func connect(writer http.ResponseWriter, request *http.Request) {
    http.Redirect(writer, request, services.GetAuthorizeUrl(), http.StatusFound)
}

// Once approval from service is done, read the token, make post request
// to callback handler (/cb) to save token.
func redirect(writer http.ResponseWriter, request *http.Request) {
    if err := redirectTmpl.Execute(writer, nil); err != nil {
        http.Error(writer, err.Error(), http.StatusInternalServerError)
    }
}

// Callback with token in post payload.
func callback(writer http.ResponseWriter, request *http.Request) {
    context := appengine.NewContext(request)
    appUser := user.Current(context)
    accessToken := AccessTokens{
        Email:       appUser.Email,
        AccessToken: request.FormValue("token"),
    }
    key := datastore.NewIncompleteKey(
        context, "AccessTokens", accessTokenKey(context, appUser.Email))
    _, err := datastore.Put(context, key, &accessToken)
    if err != nil {
        http.Error(writer, err.Error(), http.StatusInternalServerError)
        return
    }
    http.Redirect(writer, request, "/", http.StatusFound)
}

// Get list of trello boards or lists.
func trelloList(writer http.ResponseWriter, request *http.Request) {
    vars := mux.Vars(request)
    context := appengine.NewContext(request)
    appUser := user.Current(context)
    writer.Header().Set("Content-Type", "application/json")
    accessToken := getAccessToken(context, appUser.Email)
    if vars["type"] == "lists" {
        fmt.Fprintf(
            writer, services.GetBoardLists(
                context, vars["boardid"], accessToken))
        return
    }
    fmt.Fprintf(writer, services.GetBoards(context, accessToken))
}

// Save new hook from web.
func save(writer http.ResponseWriter, request *http.Request) {
    context := appengine.NewContext(request)
    appUser := user.Current(context)
    response := Response{
        Success: true,
        Reason:  "",
    }
    handler := "w" + services.GetAlphaNumberic(7)
    webhook := Webhook{
        User:    appUser.Email,
        Handler: handler,
        Date:    time.Now(),
        Count:   0,
    }
    if request.FormValue("service") == "trello" {
        webhook.Type = "Trello"
        webhook.BoardId = request.FormValue("board_id")
        webhook.BoardName = request.FormValue("board_name")
        webhook.ListId = request.FormValue("list_id")
        webhook.ListName = request.FormValue("list_name")
        services.PushToTrello(
            context, webhook.ListId,
            getAccessToken(context, webhook.User), "You are connected!", "")
    } else if request.FormValue("service") == "telegram" {
        webhook.Type = "Telegram"
        webhook.TeleChatId, webhook.TeleChatName = services.GetChatIdFromCode(
            context, request.FormValue("tele_code"))
        if webhook.TeleChatId == 0 {
            response.Success = false
            response.Reason = "Invalid code."
        } else {
            services.SendTeleMessage(
                context, "You are connected!", webhook.TeleChatId)
        }
    } else if request.FormValue("service") == "pushover" {
        webhook.Type = "Pushover"
        webhook.POUserKey = request.FormValue("po_userkey")
        status := services.SendPushoverMessage(
            context, "You are connected!", webhook.POUserKey)
        if status == 0 {
            response.Success = false
            response.Reason = "Invalid key."
        }
    } else if request.FormValue("service") == "hipchat" {
        webhook.Type = "Hipchat"
        webhook.HCToken = request.FormValue("hc_token")
        webhook.HCRoomId = request.FormValue("hc_roomid")
        status := services.SendHipchatMessage(
            context, "You are connected!", webhook.HCRoomId,
            webhook.HCToken, "green")
        if !status {
            response.Success = false
            response.Reason = "Invalid room id or token."
        }
    }
    if response.Success {
        key := datastore.NewIncompleteKey(
            context, "Webhook", webhookKey(context, handler))
        _, err := datastore.Put(context, key, &webhook)
        if err != nil {
            context.Infof("%v", err.Error())
            return
        }
        response.Handler = handler
    }
    writer.Header().Set("Content-Type", "application/json")
    resp, _ := json.Marshal(response)
    fmt.Fprintf(writer, string(resp))
}

// Telegram webhook
func telegramWebhook(writer http.ResponseWriter, request *http.Request) {
    vars := mux.Vars(request)
    context := appengine.NewContext(request)
    decoder := json.NewDecoder(request.Body)
    fmt.Fprintf(
        writer, services.Telegram(context, decoder, vars["telegramToken"]))
}

// Actual webhook handler, receive events and post it to connected services.
func hooks(writer http.ResponseWriter, request *http.Request) {
    vars := mux.Vars(request)
    handler := "w" + vars["handler"]
    context := appengine.NewContext(request)
    webhook := getWebhookFromHandler(context, handler)
    if webhook != nil {
        event, desc := services.GetEventData(request)
        context.Infof("%s: %s \n %s", webhook.Type, event, desc)
        if event != "" {
            if webhook.Type == "Trello" {
                services.PushToTrello(
                    context, webhook.ListId,
                    getAccessToken(context, webhook.User), event, desc)
            } else if webhook.Type == "Telegram" {
                services.SendTeleMessage(
                    context, event+"\n"+desc, webhook.TeleChatId)
            } else if webhook.Type == "Pushover" {
                services.SendPushoverMessage(
                    context, event+"\n"+desc, webhook.POUserKey)
            } else if webhook.Type == "Hipchat" {
                color := "red"
                if strings.Index(event, " success ") > -1 {
                    color = "green"
                }
                services.SendHipchatMessage(
                    context, event+"\n"+desc, webhook.HCRoomId,
                    webhook.HCToken, color)
            }
        }
        fmt.Fprintf(writer, "OK")
    }
}
<|endoftext|>


<commit_before>package rx

import (
	"time"

	"github.com/reactivego/rx/channel"
)

//jig:template Subject<Foo>
//jig:embeds Observable<Foo>

// SubjectFoo is a combination of an observer and observable. Subjects are
// special because they are the only reactive constructs that support
// multicasting. The items sent to it through its observer side are
// multicasted to multiple clients subscribed to its observable side.
//
// A SubjectFoo embeds ObservableFoo and FooObserveFunc. This exposes the
// methods and fields of both types on SubjectFoo. Use the ObservableFoo
// methods to subscribe to it. Use the FooObserveFunc Next, Error and Complete
// methods to feed data to it.
//
// After a subject has been terminated by calling either Error or Complete,
// it goes into terminated state. All subsequent calls to its observer side
// will be silently ignored. All subsequent subscriptions to the observable
// side will be handled according to the specific behavior of the subject.
// There are different types of subjects, see the different NewXxxSubjectFoo
// functions for more info.
//
// Important! a subject is a hot observable. This means that subscribing to
// it will block the calling goroutine while it is waiting for items and
// notifications to receive. Unless you have code on a different goroutine
// already feeding into the subject, your subscribe will deadlock.
// Alternatively, you could subscribe on a goroutine as shown in the example.
type SubjectFoo struct {
	ObservableFoo
	FooObserveFunc
}

//jig:template NewSubject<Foo>
//jig:needs Subject<Foo>

// NewSubjectFoo creates a new Subject. After the subject is
// terminated, all subsequent subscriptions to the observable side will be
// terminated immediately with either an Error or Complete notification send to
// the subscribing client
//
// Note that this implementation is blocking. When no subcribers are present
// then the data can flow freely. But when there are subscribers, the observable
// goroutine is blocked until all subscribers have processed the next, error or
// complete notification.
func NewSubjectFoo() SubjectFoo {
	ch := channel.NewChan(1, 16 /*max enpoints*/)

	observable := Observable(func(observe ObserveFunc, subscribeOn Scheduler, subscriber Subscriber) {
		ep, err := ch.NewEndpoint(0)
		if err != nil {
			observe(nil, err, true)
			return
		}
		observable := Create(func(observer Observer) {
			receive := func(value interface{}, err error, closed bool) bool {
				if !closed {
					observer.Next(value)
				} else {
					observer.Error(err)
				}
				return !observer.Closed()
			}
			ep.Range(receive, 0)
		})
		observable(observe, subscribeOn, subscriber.Add(ep.Cancel))
	})

	observer := func(next foo, err error, done bool) {
		if !ch.Closed() {
			if !done {
				ch.FastSend(next)
			} else {
				ch.Close(err)
			}
		}
	}

	return SubjectFoo{observable.AsObservableFoo(), observer}
}

//jig:template MaxReplayCapacity

// MaxReplayCapacity is the maximum size of a replay buffer. Can be modified.
var MaxReplayCapacity = 16383

//jig:template NewReplaySubject<Foo>
//jig:needs Subject<Foo>, MaxReplayCapacity

// NewReplaySubjectFoo creates a new ReplaySubject. ReplaySubject ensures that
// all observers see the same sequence of emitted items, even if they
// subscribe after. When bufferCapacity argument is 0, then MaxReplayCapacity is
// used (currently 16383). When windowDuration argument is 0, then entries added
// to the buffer will remain fresh forever.
//
// Note that this implementation is non-blocking. When no subscribers are
// present the buffer fills up to bufferCapacity after which new items will
// start overwriting the oldest ones according to the FIFO principle.
// If a subscriber cannot keep up with the data rate of the source observable,
// eventually the buffer for the subscriber will overflow. At that moment the
// subscriber will receive an ErrMissingBackpressure error.
func NewReplaySubjectFoo(bufferCapacity int, windowDuration time.Duration) SubjectFoo {
	if bufferCapacity == 0 {
		bufferCapacity = MaxReplayCapacity
	}
	ch := channel.NewChan(bufferCapacity, 16 /*max enpoints*/)

	observable := Observable(func(observe ObserveFunc, subscribeOn Scheduler, subscriber Subscriber) {
		ep, err := ch.NewEndpoint(channel.ReplayAll)
		if err != nil {
			observe(nil, err, true)
			return
		}
		observable := Create(func(observer Observer) {
			receive := func(value interface{}, err error, closed bool) bool {
				if !closed {
					observer.Next(value)
				} else {
					observer.Error(err)
				}
				return !observer.Closed()
			}
			ep.Range(receive, windowDuration)
		})
		observable(observe, subscribeOn, subscriber.Add(ep.Cancel))
	})

	observer := func(next foo, err error, done bool) {
		if !ch.Closed() {
			if !done {
				ch.Send(next)
			} else {
				ch.Close(err)
			}
		}
	}

	return SubjectFoo{observable.AsObservableFoo(), observer}
}
<commit_msg>Doc fixes.<commit_after>package rx

import (
	"time"

	"github.com/reactivego/rx/channel"
)

//jig:template Subject<Foo>
//jig:embeds Observable<Foo>

// SubjectFoo is a combination of an observer and observable. Subjects are
// special because they are the only reactive constructs that support
// multicasting. The items sent to it through its observer side are
// multicasted to multiple clients subscribed to its observable side.
//
// A SubjectFoo embeds ObservableFoo and FooObserveFunc. This exposes the
// methods and fields of both types on SubjectFoo. Use the ObservableFoo
// methods to subscribe to it. Use the FooObserveFunc Next, Error and Complete
// methods to feed data to it.
//
// After a subject has been terminated by calling either Error or Complete,
// it goes into terminated state. All subsequent calls to its observer side
// will be silently ignored. All subsequent subscriptions to the observable
// side will be handled according to the specific behavior of the subject.
// There are different types of subjects, see the different NewXxxSubjectFoo
// functions for more info.
//
// Important! a subject is a hot observable. This means that subscribing to
// it will block the calling goroutine while it is waiting for items and
// notifications to receive. Unless you have code on a different goroutine
// already feeding into the subject, your subscribe will deadlock.
// Alternatively, you could subscribe on a goroutine as shown in the example.
type SubjectFoo struct {
	ObservableFoo
	FooObserveFunc
}

//jig:template NewSubject<Foo>
//jig:needs Subject<Foo>

// NewSubjectFoo creates a new Subject. After the subject is
// terminated, all subsequent subscriptions to the observable side will be
// terminated immediately with either an Error or Complete notification send to
// the subscribing client
//
// Note that this implementation is blocking. When no subcribers are present
// then the data can flow freely. But when there are subscribers, the observable
// goroutine is blocked until all subscribers have processed the next, error or
// complete notification.
func NewSubjectFoo() SubjectFoo {
	ch := channel.NewChan(1, 16 /*max endpoints*/)

	observable := Observable(func(observe ObserveFunc, subscribeOn Scheduler, subscriber Subscriber) {
		ep, err := ch.NewEndpoint(0)
		if err != nil {
			observe(nil, err, true)
			return
		}
		observable := Create(func(observer Observer) {
			receive := func(value interface{}, err error, closed bool) bool {
				if !closed {
					observer.Next(value)
				} else {
					observer.Error(err)
				}
				return !observer.Closed()
			}
			ep.Range(receive, 0)
		})
		observable(observe, subscribeOn, subscriber.Add(ep.Cancel))
	})

	observer := func(next foo, err error, done bool) {
		if !ch.Closed() {
			if !done {
				ch.FastSend(next)
			} else {
				ch.Close(err)
			}
		}
	}

	return SubjectFoo{observable.AsObservableFoo(), observer}
}

//jig:template MaxReplayCapacity

// MaxReplayCapacity is the maximum size of a replay buffer. Can be modified.
var MaxReplayCapacity = 16383

//jig:template NewReplaySubject<Foo>
//jig:needs Subject<Foo>, MaxReplayCapacity

// NewReplaySubjectFoo creates a new ReplaySubject. ReplaySubject ensures that
// all observers see the same sequence of emitted items, even if they
// subscribe after. When bufferCapacity argument is 0, then MaxReplayCapacity is
// used (currently 16383). When windowDuration argument is 0, then entries added
// to the buffer will remain fresh forever.
//
// Note that this implementation is non-blocking. When no subscribers are
// present the buffer fills up to bufferCapacity after which new items will
// start overwriting the oldest ones according to the FIFO principle.
// If a subscriber cannot keep up with the data rate of the source observable,
// eventually the buffer for the subscriber will overflow. At that moment the
// subscriber will receive an ErrMissingBackpressure error.
func NewReplaySubjectFoo(bufferCapacity int, windowDuration time.Duration) SubjectFoo {
	if bufferCapacity == 0 {
		bufferCapacity = MaxReplayCapacity
	}
	ch := channel.NewChan(bufferCapacity, 16 /*max endpoints*/)

	observable := Observable(func(observe ObserveFunc, subscribeOn Scheduler, subscriber Subscriber) {
		ep, err := ch.NewEndpoint(channel.ReplayAll)
		if err != nil {
			observe(nil, err, true)
			return
		}
		observable := Create(func(observer Observer) {
			receive := func(value interface{}, err error, closed bool) bool {
				if !closed {
					observer.Next(value)
				} else {
					observer.Error(err)
				}
				return !observer.Closed()
			}
			ep.Range(receive, windowDuration)
		})
		observable(observe, subscribeOn, subscriber.Add(ep.Cancel))
	})

	observer := func(next foo, err error, done bool) {
		if !ch.Closed() {
			if !done {
				ch.Send(next)
			} else {
				ch.Close(err)
			}
		}
	}

	return SubjectFoo{observable.AsObservableFoo(), observer}
}
<|endoftext|>


<commit_before>// This packages generates naive text summay
package summary

import (
	"sort"
)

const minSentences = 2

type rankMap map[string]int

// Tokenizer defines the interface for the tokenizing functions
type Tokenizer interface {
	GetParagraphs(text string) []string
	GetSentences(paragraph string) []string
	GetWords(sentence string) []string
	FormatSentence(sentence string) string
}

func sentencesIntersection(s1, s2 []string) int {
	result := 0
	if len(s1) == 0 || len(s2) == 0 {
		return result
	}
	sort.StringSlice(s2).Sort()
	lenS2 := len(s2)
	for _, w := range s1 {
		i := sort.SearchStrings(s2, w)
		if i < lenS2 && s2[i] == w {
			result++
		}
	}
	return result
}

func sum(a []int) int {
	sum := 0
	for _, i := range a {
		sum += i
	}
	return sum
}

func getRanks(text string, t Tokenizer) rankMap {
	sentences := t.GetSentences(text)
	n := len(sentences)
	var matrix = make([][]int, n)
	for i := 0; i < n; i++ {
		matrix[i] = make([]int, n)
		for j := 0; j < n; j++ {
			matrix[i][j] = sentencesIntersection(t.GetWords(sentences[i]), t.GetWords(sentences[j]))
		}
	}
	ranks := make(rankMap)
	for i, sentence := range sentences {
		ranks[t.FormatSentence(sentence)] = sum(matrix[i])
	}
	return ranks
}

func getBestSentence(text string, ranks rankMap, t Tokenizer) string {
	bestSentence := ""
	sentences := t.GetSentences(text)
	if len(sentences) < minSentences {
		return bestSentence
	}
	maxRank := 0
	for _, s := range sentences {
		stripped := t.FormatSentence(s)
		rank, ok := ranks[stripped]
		if ok && rank > maxRank {
			maxRank = rank
			bestSentence = s
		}
	}
	return bestSentence
}

func getSummary(text string, ranks rankMap, t Tokenizer) string {
	paragraphs := t.GetParagraphs(text)
	var result string
	for _, p := range paragraphs {
		if sentence := getBestSentence(p, ranks, t); sentence != "" {
			result += sentence + "\n"
		}
	}
	return result
}

// Summarize generates summary for the text using the provided tokenizer
func Summarize(text string, t Tokenizer) string {
	ranks := getRanks(text, t)
	return getSummary(text, ranks, t)
}
<commit_msg>No need for matrix<commit_after>// This packages generates naive text summay
package summary

import (
	"sort"
)

const minSentences = 2

type rankMap map[string]int

// Tokenizer defines the interface for the tokenizing functions
type Tokenizer interface {
	GetParagraphs(text string) []string
	GetSentences(paragraph string) []string
	GetWords(sentence string) []string
	FormatSentence(sentence string) string
}

func sentencesIntersection(s1, s2 []string) int {
	result := 0
	if len(s1) == 0 || len(s2) == 0 {
		return result
	}
	sort.StringSlice(s2).Sort()
	lenS2 := len(s2)
	for _, w := range s1 {
		i := sort.SearchStrings(s2, w)
		if i < lenS2 && s2[i] == w {
			result++
		}
	}
	return result
}

func sum(a []int) int {
	sum := 0
	for _, i := range a {
		sum += i
	}
	return sum
}

func getRanks(text string, t Tokenizer) rankMap {
	sentences := t.GetSentences(text)
	n := len(sentences)
	ranks := make(rankMap)
	for i := 0; i < n; i++ {
		rank := 0
		for j := 0; j < n; j++ {
			rank += sentencesIntersection(t.GetWords(sentences[i]), t.GetWords(sentences[j]))
		}
		ranks[t.FormatSentence(sentences[i])] = rank
	}
	return ranks
}

func getBestSentence(text string, ranks rankMap, t Tokenizer) string {
	bestSentence := ""
	sentences := t.GetSentences(text)
	if len(sentences) < minSentences {
		return bestSentence
	}
	maxRank := 0
	for _, s := range sentences {
		stripped := t.FormatSentence(s)
		rank, ok := ranks[stripped]
		if ok && rank > maxRank {
			maxRank = rank
			bestSentence = s
		}
	}
	return bestSentence
}

func getSummary(text string, ranks rankMap, t Tokenizer) string {
	paragraphs := t.GetParagraphs(text)
	var result string
	for _, p := range paragraphs {
		if sentence := getBestSentence(p, ranks, t); sentence != "" {
			result += sentence + "\n"
		}
	}
	return result
}

// Summarize generates summary for the text using the provided tokenizer
func Summarize(text string, t Tokenizer) string {
	ranks := getRanks(text, t)
	return getSummary(text, ranks, t)
}
<|endoftext|>


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

// This file defines operands and associated operations.

package types

import (
	"bytes"
	"go/ast"
	"go/constant"
	"go/token"
)

// An operandMode specifies the (addressing) mode of an operand.
type operandMode byte

const (
	invalid   operandMode = iota // operand is invalid
	novalue                      // operand represents no value (result of a function call w/o result)
	builtin                      // operand is a built-in function
	typexpr                      // operand is a type
	constant_                    // operand is a constant; the operand's typ is a Basic type
	variable                     // operand is an addressable variable
	mapindex                     // operand is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment)
	value                        // operand is a computed value
	commaok                      // like value, but operand may be used in a comma,ok expression
)

var operandModeString = [...]string{
	invalid:   "invalid operand",
	novalue:   "no value",
	builtin:   "built-in",
	typexpr:   "type",
	constant_: "constant",
	variable:  "variable",
	mapindex:  "map index expression",
	value:     "value",
	commaok:   "comma, ok expression",
}

// An operand represents an intermediate value during type checking.
// Operands have an (addressing) mode, the expression evaluating to
// the operand, the operand's type, a value for constants, and an id
// for built-in functions.
// The zero value of operand is a ready to use invalid operand.
//
type operand struct {
	mode operandMode
	expr ast.Expr
	typ  Type
	val  constant.Value
	id   builtinId
}

// pos returns the position of the expression corresponding to x.
// If x is invalid the position is token.NoPos.
//
func (x *operand) pos() token.Pos {
	// x.expr may not be set if x is invalid
	if x.expr == nil {
		return token.NoPos
	}
	return x.expr.Pos()
}

// Operand string formats
// (not all "untyped" cases can appear due to the type system,
// but they fall out naturally here)
//
// mode       format
//
// invalid    <expr> (               <mode>                    )
// novalue    <expr> (               <mode>                    )
// builtin    <expr> (               <mode>                    )
// typexpr    <expr> (               <mode>                    )
//
// constant   <expr> (<untyped kind> <mode>                    )
// constant   <expr> (               <mode>       of type <typ>)
// constant   <expr> (<untyped kind> <mode> <val>              )
// constant   <expr> (               <mode> <val> of type <typ>)
//
// variable   <expr> (<untyped kind> <mode>                    )
// variable   <expr> (               <mode>       of type <typ>)
//
// mapindex   <expr> (<untyped kind> <mode>                    )
// mapindex   <expr> (               <mode>       of type <typ>)
//
// value      <expr> (<untyped kind> <mode>                    )
// value      <expr> (               <mode>       of type <typ>)
//
// commaok    <expr> (<untyped kind> <mode>                    )
// commaok    <expr> (               <mode>       of type <typ>)
//
func operandString(x *operand, qf Qualifier) string {
	var buf bytes.Buffer

	var expr string
	if x.expr != nil {
		expr = ExprString(x.expr)
	} else {
		switch x.mode {
		case builtin:
			expr = predeclaredFuncs[x.id].name
		case typexpr:
			expr = TypeString(x.typ, qf)
		case constant_:
			expr = x.val.String()
		}
	}

	// <expr> (
	if expr != "" {
		buf.WriteString(expr)
		buf.WriteString(" (")
	}

	// <untyped kind>
	hasType := false
	switch x.mode {
	case invalid, novalue, builtin, typexpr:
		// no type
	default:
		// has type
		if isUntyped(x.typ) {
			buf.WriteString(x.typ.(*Basic).name)
			buf.WriteByte(' ')
			break
		}
		hasType = true
	}

	// <mode>
	buf.WriteString(operandModeString[x.mode])

	// <val>
	if x.mode == constant_ {
		if s := x.val.String(); s != expr {
			buf.WriteByte(' ')
			buf.WriteString(s)
		}
	}

	// <typ>
	if hasType {
		if x.typ != Typ[Invalid] {
			buf.WriteString(" of type ")
			WriteType(&buf, x.typ, qf)
		} else {
			buf.WriteString(" with invalid type")
		}
	}

	// )
	if expr != "" {
		buf.WriteByte(')')
	}

	return buf.String()
}

func (x *operand) String() string {
	return operandString(x, nil)
}

// setConst sets x to the untyped constant for literal lit.
func (x *operand) setConst(tok token.Token, lit string) {
	var kind BasicKind
	switch tok {
	case token.INT:
		kind = UntypedInt
	case token.FLOAT:
		kind = UntypedFloat
	case token.IMAG:
		kind = UntypedComplex
	case token.CHAR:
		kind = UntypedRune
	case token.STRING:
		kind = UntypedString
	default:
		unreachable()
	}

	x.mode = constant_
	x.typ = Typ[kind]
	x.val = constant.MakeFromLiteral(lit, tok, 0)
}

// isNil reports whether x is the nil value.
func (x *operand) isNil() bool {
	return x.mode == value && x.typ == Typ[UntypedNil]
}

// TODO(gri) The functions operand.assignableTo, checker.convertUntyped,
//           checker.representable, and checker.assignment are
//           overlapping in functionality. Need to simplify and clean up.

// assignableTo reports whether x is assignable to a variable of type T.
// If the result is false and a non-nil reason is provided, it may be set
// to a more detailed explanation of the failure (result != "").
func (x *operand) assignableTo(conf *Config, T Type, reason *string) bool {
	if x.mode == invalid || T == Typ[Invalid] {
		return true // avoid spurious errors
	}

	V := x.typ

	// x's type is identical to T
	if Identical(V, T) {
		return true
	}

	Vu := V.Underlying()
	Tu := T.Underlying()

	// x is an untyped value representable by a value of type T
	// TODO(gri) This is borrowing from checker.convertUntyped and
	//           checker.representable. Need to clean up.
	if isUntyped(Vu) {
		switch t := Tu.(type) {
		case *Basic:
			if x.isNil() && t.kind == UnsafePointer {
				return true
			}
			if x.mode == constant_ {
				return representableConst(x.val, conf, t, nil)
			}
			// The result of a comparison is an untyped boolean,
			// but may not be a constant.
			if Vb, _ := Vu.(*Basic); Vb != nil {
				return Vb.kind == UntypedBool && isBoolean(Tu)
			}
		case *Interface:
			return x.isNil() || t.Empty()
		case *Pointer, *Signature, *Slice, *Map, *Chan:
			return x.isNil()
		}
	}
	// Vu is typed

	// x's type V and T have identical underlying types
	// and at least one of V or T is not a named type
	if Identical(Vu, Tu) && (!isNamed(V) || !isNamed(T)) {
		return true
	}

	// T is an interface type and x implements T
	if Ti, ok := Tu.(*Interface); ok {
		if m, wrongType := MissingMethod(x.typ, Ti, true); m != nil /* Implements(x.typ, Ti) */ {
			if reason != nil {
				if wrongType {
					*reason = "wrong type for method " + m.Name()
				} else {
					*reason = "missing method " + m.Name()
				}
			}
			return false
		}
		return true
	}

	// x is a bidirectional channel value, T is a channel
	// type, x's type V and T have identical element types,
	// and at least one of V or T is not a named type
	if Vc, ok := Vu.(*Chan); ok && Vc.dir == SendRecv {
		if Tc, ok := Tu.(*Chan); ok && Identical(Vc.elem, Tc.elem) {
			return !isNamed(V) || !isNamed(T)
		}
	}

	return false
}
<commit_msg>go/types: more robust operand printing<commit_after>// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file defines operands and associated operations.

package types

import (
	"bytes"
	"go/ast"
	"go/constant"
	"go/token"
)

// An operandMode specifies the (addressing) mode of an operand.
type operandMode byte

const (
	invalid   operandMode = iota // operand is invalid
	novalue                      // operand represents no value (result of a function call w/o result)
	builtin                      // operand is a built-in function
	typexpr                      // operand is a type
	constant_                    // operand is a constant; the operand's typ is a Basic type
	variable                     // operand is an addressable variable
	mapindex                     // operand is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment)
	value                        // operand is a computed value
	commaok                      // like value, but operand may be used in a comma,ok expression
)

var operandModeString = [...]string{
	invalid:   "invalid operand",
	novalue:   "no value",
	builtin:   "built-in",
	typexpr:   "type",
	constant_: "constant",
	variable:  "variable",
	mapindex:  "map index expression",
	value:     "value",
	commaok:   "comma, ok expression",
}

// An operand represents an intermediate value during type checking.
// Operands have an (addressing) mode, the expression evaluating to
// the operand, the operand's type, a value for constants, and an id
// for built-in functions.
// The zero value of operand is a ready to use invalid operand.
//
type operand struct {
	mode operandMode
	expr ast.Expr
	typ  Type
	val  constant.Value
	id   builtinId
}

// pos returns the position of the expression corresponding to x.
// If x is invalid the position is token.NoPos.
//
func (x *operand) pos() token.Pos {
	// x.expr may not be set if x is invalid
	if x.expr == nil {
		return token.NoPos
	}
	return x.expr.Pos()
}

// Operand string formats
// (not all "untyped" cases can appear due to the type system,
// but they fall out naturally here)
//
// mode       format
//
// invalid    <expr> (               <mode>                    )
// novalue    <expr> (               <mode>                    )
// builtin    <expr> (               <mode>                    )
// typexpr    <expr> (               <mode>                    )
//
// constant   <expr> (<untyped kind> <mode>                    )
// constant   <expr> (               <mode>       of type <typ>)
// constant   <expr> (<untyped kind> <mode> <val>              )
// constant   <expr> (               <mode> <val> of type <typ>)
//
// variable   <expr> (<untyped kind> <mode>                    )
// variable   <expr> (               <mode>       of type <typ>)
//
// mapindex   <expr> (<untyped kind> <mode>                    )
// mapindex   <expr> (               <mode>       of type <typ>)
//
// value      <expr> (<untyped kind> <mode>                    )
// value      <expr> (               <mode>       of type <typ>)
//
// commaok    <expr> (<untyped kind> <mode>                    )
// commaok    <expr> (               <mode>       of type <typ>)
//
func operandString(x *operand, qf Qualifier) string {
	var buf bytes.Buffer

	var expr string
	if x.expr != nil {
		expr = ExprString(x.expr)
	} else {
		switch x.mode {
		case builtin:
			expr = predeclaredFuncs[x.id].name
		case typexpr:
			expr = TypeString(x.typ, qf)
		case constant_:
			expr = x.val.String()
		}
	}

	// <expr> (
	if expr != "" {
		buf.WriteString(expr)
		buf.WriteString(" (")
	}

	// <untyped kind>
	hasType := false
	switch x.mode {
	case invalid, novalue, builtin, typexpr:
		// no type
	default:
		// should have a type, but be cautious (don't crash during printing)
		if x.typ != nil {
			if isUntyped(x.typ) {
				buf.WriteString(x.typ.(*Basic).name)
				buf.WriteByte(' ')
				break
			}
			hasType = true
		}
	}

	// <mode>
	buf.WriteString(operandModeString[x.mode])

	// <val>
	if x.mode == constant_ {
		if s := x.val.String(); s != expr {
			buf.WriteByte(' ')
			buf.WriteString(s)
		}
	}

	// <typ>
	if hasType {
		if x.typ != Typ[Invalid] {
			buf.WriteString(" of type ")
			WriteType(&buf, x.typ, qf)
		} else {
			buf.WriteString(" with invalid type")
		}
	}

	// )
	if expr != "" {
		buf.WriteByte(')')
	}

	return buf.String()
}

func (x *operand) String() string {
	return operandString(x, nil)
}

// setConst sets x to the untyped constant for literal lit.
func (x *operand) setConst(tok token.Token, lit string) {
	var kind BasicKind
	switch tok {
	case token.INT:
		kind = UntypedInt
	case token.FLOAT:
		kind = UntypedFloat
	case token.IMAG:
		kind = UntypedComplex
	case token.CHAR:
		kind = UntypedRune
	case token.STRING:
		kind = UntypedString
	default:
		unreachable()
	}

	x.mode = constant_
	x.typ = Typ[kind]
	x.val = constant.MakeFromLiteral(lit, tok, 0)
}

// isNil reports whether x is the nil value.
func (x *operand) isNil() bool {
	return x.mode == value && x.typ == Typ[UntypedNil]
}

// TODO(gri) The functions operand.assignableTo, checker.convertUntyped,
//           checker.representable, and checker.assignment are
//           overlapping in functionality. Need to simplify and clean up.

// assignableTo reports whether x is assignable to a variable of type T.
// If the result is false and a non-nil reason is provided, it may be set
// to a more detailed explanation of the failure (result != "").
func (x *operand) assignableTo(conf *Config, T Type, reason *string) bool {
	if x.mode == invalid || T == Typ[Invalid] {
		return true // avoid spurious errors
	}

	V := x.typ

	// x's type is identical to T
	if Identical(V, T) {
		return true
	}

	Vu := V.Underlying()
	Tu := T.Underlying()

	// x is an untyped value representable by a value of type T
	// TODO(gri) This is borrowing from checker.convertUntyped and
	//           checker.representable. Need to clean up.
	if isUntyped(Vu) {
		switch t := Tu.(type) {
		case *Basic:
			if x.isNil() && t.kind == UnsafePointer {
				return true
			}
			if x.mode == constant_ {
				return representableConst(x.val, conf, t, nil)
			}
			// The result of a comparison is an untyped boolean,
			// but may not be a constant.
			if Vb, _ := Vu.(*Basic); Vb != nil {
				return Vb.kind == UntypedBool && isBoolean(Tu)
			}
		case *Interface:
			return x.isNil() || t.Empty()
		case *Pointer, *Signature, *Slice, *Map, *Chan:
			return x.isNil()
		}
	}
	// Vu is typed

	// x's type V and T have identical underlying types
	// and at least one of V or T is not a named type
	if Identical(Vu, Tu) && (!isNamed(V) || !isNamed(T)) {
		return true
	}

	// T is an interface type and x implements T
	if Ti, ok := Tu.(*Interface); ok {
		if m, wrongType := MissingMethod(x.typ, Ti, true); m != nil /* Implements(x.typ, Ti) */ {
			if reason != nil {
				if wrongType {
					*reason = "wrong type for method " + m.Name()
				} else {
					*reason = "missing method " + m.Name()
				}
			}
			return false
		}
		return true
	}

	// x is a bidirectional channel value, T is a channel
	// type, x's type V and T have identical element types,
	// and at least one of V or T is not a named type
	if Vc, ok := Vu.(*Chan); ok && Vc.dir == SendRecv {
		if Tc, ok := Tu.(*Chan); ok && Identical(Vc.elem, Tc.elem) {
			return !isNamed(V) || !isNamed(T)
		}
	}

	return false
}
<|endoftext|>


<commit_before>package svg

import (
	"bytes"
	"fmt"
	"text/template"
)

const svgDiagram = `<?xml version="1.0" ?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="{{.TotalWidth}}px" height="{{.TotalHeight}}px">
<!-- Generated by simple FlowDev draw-svg tool. -->
	<rect fill="rgb(255,255,255)" fill-opacity="1" stroke="none" stroke-opacity="1" stroke-width="0.0" width="{{.TotalWidth}}" height="{{.TotalHeight}}" x="0" y="0"/>
{{- range .Arrows}}
	<line stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="2.5" x1="{{.X1}}" y1="{{.Y1}}" x2="{{.X2}}" y2="{{.Y2}}"/>
	<line stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="2.5" x1="{{.XTip1}}" y1="{{.YTip1}}" x2="{{.X2}}" y2="{{.Y2}}"/>
	<line stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="2.5" x1="{{.XTip2}}" y1="{{.YTip2}}" x2="{{.X2}}" y2="{{.Y2}}"/>
{{end}}
{{- range .Rects}}
{{- if .IsPlugin}}
	<rect fill="rgb(32,224,32)" fill-opacity="1.0" stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="2.5" width="{{.Width}}" height="{{.Height}}" x="{{.X}}" y="{{.Y}}"/>
{{- else}}
	<rect fill="rgb(96,196,255)" fill-opacity="1.0" stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="2.5" width="{{.Width}}" height="{{.Height}}" x="{{.X}}" y="{{.Y}}" rx="10" ry="10"/>
{{- end}}
{{- end}}
{{range .Lines}}
	<line stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="1.0" x1="{{.X1}}" y1="{{.Y1}}" x2="{{.X2}}" y2="{{.Y2}}"/>
{{- end}}
{{range .Texts}}
	<text fill="rgb(0,0,0)" fill-opacity="1.0" font-family="monospace" font-size="16" x="{{.X}}" y="{{.Y}}" textLength="{{.Width}}" lengthAdjust="spacingAndGlyphs">{{.Text}}</text>
{{- end}}
</svg>
`

// Arrow contains all information for displaying an Arrow including data type
// and ports.
type Arrow struct {
	DataType string
	HasSrcOp bool
	SrcPort  string
	HasDstOp bool
	DstPort  string
}

// Rect just contains the text lines to display in a rectangle.
type Rect struct {
	Text []string
}

// Plugin is a helper operation that is used inside a proper operation.
type Plugin struct {
	Title string
	Rects []*Rect
}

// Op holds all data to describe a single operation including possible plugins.
type Op struct {
	Main    *Rect
	Plugins []*Plugin
}

// Split contains data for multiple paths/arrows originating from a single Op.
type Split struct {
	Shapes [][]interface{}
}

// Merge holds data for merging multiple paths/arrows into a single Op.
type Merge struct {
	ID   string
	Size int
}

// Flow contains data for a whole flow.
// The data is organized in rows and individual shapes per row.
// Valid shapes are Arrow, Op, Split and Merge.
type Flow struct {
	Shapes [][]interface{}
}

type svgArrow struct {
	X1, Y1       int
	X2, Y2       int
	XTip1, YTip1 int
	XTip2, YTip2 int
}

type svgRect struct {
	X, Y     int
	Width    int
	Height   int
	IsPlugin bool
}

type svgLine struct {
	X1, Y1 int
	X2, Y2 int
}

type svgText struct {
	X, Y  int
	Width int
	Text  string
}

type svgFlow struct {
	TotalWidth  int
	TotalHeight int
	Arrows      []*svgArrow
	Rects       []*svgRect
	Lines       []*svgLine
	Texts       []*svgText

	completedMerge *myMergeData
	allMerges      map[string]*myMergeData
}

type myMergeData struct {
	moveData []*moveData
	curSize  int
	x0, y0   int
	yn       int
}
type moveData struct {
	arrow       *svgArrow
	dataText    *svgText
	dstPortText *svgText
	yn          int
}

var tmpl = template.Must(template.New("diagram").Parse(svgDiagram))

// FromFlowData creates a SVG diagram from flow data.
// If the flow data isn't valid or the SVG diagram can't be created with its
// template, an error is returned.
func FromFlowData(f *Flow) ([]byte, error) {
	var err error
	f, err = validateFlowData(f)
	if err != nil {
		return nil, err
	}

	sf := flowDataToSVGFlow(f)

	return svgFlowToBytes(sf)
}

func validateFlowData(f *Flow) (*Flow, error) {
	if f == nil || len(f.Shapes) <= 0 {
		return nil, fmt.Errorf("flow is empty")
	}
	for i, row := range f.Shapes {
		for j, ishape := range row {
			switch ishape.(type) {
			case *Arrow, *Op, *Split, *Merge:
				break
			default:
				return nil, fmt.Errorf(
					"unsupported shape type %T at row index %d and column index %d",
					ishape, i, j)
			}
		}
	}
	return f, nil
}

func svgFlowToBytes(sf *svgFlow) ([]byte, error) {
	buf := bytes.Buffer{}
	err := tmpl.Execute(&buf, sf)
	if err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func flowDataToSVGFlow(f *Flow) *svgFlow {
	sf, x, y := initSVGData()
	sf, x, y = shapesToSVG(
		f.Shapes,
		sf, x, y,
		arrowDataToSVG,
		opDataToSVG,
		rectDataToSVG,
		splitDataToSVG,
		mergeDataToSVG,
	)
	return adjustDimensions(sf, x, y)
}

func initSVGData() (sf *svgFlow, x0, y0 int) {
	return &svgFlow{
		Arrows: make([]*svgArrow, 0, 64),
		Rects:  make([]*svgRect, 0, 64),
		Lines:  make([]*svgLine, 0, 64),
		Texts:  make([]*svgText, 0, 64),

		allMerges: make(map[string]*myMergeData),
	}, 2, 1
}
func adjustDimensions(sf *svgFlow, xn, yn int) *svgFlow {
	sf.TotalWidth = xn + 2
	sf.TotalHeight = yn + 3
	return sf
}

func shapesToSVG(
	shapes [][]interface{}, sf *svgFlow, x0 int, y0 int,
	pluginArrowDataToSVG func(*Arrow, *svgFlow, int, int) (*svgFlow, int, int, *moveData),
	pluginOpDataToSVG func(*Op, *svgFlow, int, int) (*svgFlow, *svgRect, int, int, int),
	pluginRectDataToSVG func(*Rect, *svgFlow, int, int) (*svgFlow, int, int),
	pluginSplitDataToSVG func(*Split, *svgFlow, *svgRect, int, int) (*svgFlow, int, int),
	pluginMergeDataToSVG func(*Merge, *svgFlow, *moveData, int, int) *myMergeData,
) (nsf *svgFlow, xn, yn int) {
	var xmax, ymax int
	var mod *moveData
	var lsr *svgRect

	for _, ss := range shapes {
		x := x0
		lsr = nil
		if len(ss) < 1 {
			y0 += 48
			continue
		}
		for _, is := range ss {
			y := y0
			switch s := is.(type) {
			case *Arrow:
				sf, x, y, mod = pluginArrowDataToSVG(s, sf, x, y)
				lsr = nil
			case *Op:
				sf, lsr, y0, x, y = pluginOpDataToSVG(s, sf, x, y0)
				sf.completedMerge = nil
			case *Rect:
				sf, x, y = pluginRectDataToSVG(s, sf, x, y)
			case *Split:
				sf, x, y = pluginSplitDataToSVG(s, sf, lsr, x, y)
				lsr = nil
			case *Merge:
				sf.completedMerge = pluginMergeDataToSVG(s, sf, mod, x, y)
				mod = nil
			default:
				panic(fmt.Sprintf("unsupported type: %T", is))
			}

			ymax = max(ymax, y)
		}
		xmax = max(xmax, x)
		y0 = ymax + 5
	}
	return sf, xmax, ymax
}

func min(a, b int) int {
	if a <= b {
		return a
	}
	return b
}
func max(a, b int) int {
	if a >= b {
		return a
	}
	return b
}
<commit_msg>Enhance validation of flows<commit_after>package svg

import (
	"bytes"
	"fmt"
	"text/template"
)

const svgDiagram = `<?xml version="1.0" ?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="{{.TotalWidth}}px" height="{{.TotalHeight}}px">
<!-- Generated by simple FlowDev draw-svg tool. -->
	<rect fill="rgb(255,255,255)" fill-opacity="1" stroke="none" stroke-opacity="1" stroke-width="0.0" width="{{.TotalWidth}}" height="{{.TotalHeight}}" x="0" y="0"/>
{{- range .Arrows}}
	<line stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="2.5" x1="{{.X1}}" y1="{{.Y1}}" x2="{{.X2}}" y2="{{.Y2}}"/>
	<line stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="2.5" x1="{{.XTip1}}" y1="{{.YTip1}}" x2="{{.X2}}" y2="{{.Y2}}"/>
	<line stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="2.5" x1="{{.XTip2}}" y1="{{.YTip2}}" x2="{{.X2}}" y2="{{.Y2}}"/>
{{end}}
{{- range .Rects}}
{{- if .IsPlugin}}
	<rect fill="rgb(32,224,32)" fill-opacity="1.0" stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="2.5" width="{{.Width}}" height="{{.Height}}" x="{{.X}}" y="{{.Y}}"/>
{{- else}}
	<rect fill="rgb(96,196,255)" fill-opacity="1.0" stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="2.5" width="{{.Width}}" height="{{.Height}}" x="{{.X}}" y="{{.Y}}" rx="10" ry="10"/>
{{- end}}
{{- end}}
{{range .Lines}}
	<line stroke="rgb(0,0,0)" stroke-opacity="1.0" stroke-width="1.0" x1="{{.X1}}" y1="{{.Y1}}" x2="{{.X2}}" y2="{{.Y2}}"/>
{{- end}}
{{range .Texts}}
	<text fill="rgb(0,0,0)" fill-opacity="1.0" font-family="monospace" font-size="16" x="{{.X}}" y="{{.Y}}" textLength="{{.Width}}" lengthAdjust="spacingAndGlyphs">{{.Text}}</text>
{{- end}}
</svg>
`

// Arrow contains all information for displaying an Arrow including data type
// and ports.
type Arrow struct {
	DataType string
	HasSrcOp bool
	SrcPort  string
	HasDstOp bool
	DstPort  string
}

// Rect just contains the text lines to display in a rectangle.
type Rect struct {
	Text []string
}

// Plugin is a helper operation that is used inside a proper operation.
type Plugin struct {
	Title string
	Rects []*Rect
}

// Op holds all data to describe a single operation including possible plugins.
type Op struct {
	Main    *Rect
	Plugins []*Plugin
}

// Split contains data for multiple paths/arrows originating from a single Op.
type Split struct {
	Shapes [][]interface{}
}

// Merge holds data for merging multiple paths/arrows into a single Op.
type Merge struct {
	ID   string
	Size int
}

// Flow contains data for a whole flow.
// The data is organized in rows and individual shapes per row.
// Valid shapes are Arrow, Op, Split and Merge.
type Flow struct {
	Shapes [][]interface{}
}

type svgArrow struct {
	X1, Y1       int
	X2, Y2       int
	XTip1, YTip1 int
	XTip2, YTip2 int
}

type svgRect struct {
	X, Y     int
	Width    int
	Height   int
	IsPlugin bool
}

type svgLine struct {
	X1, Y1 int
	X2, Y2 int
}

type svgText struct {
	X, Y  int
	Width int
	Text  string
}

type svgFlow struct {
	TotalWidth  int
	TotalHeight int
	Arrows      []*svgArrow
	Rects       []*svgRect
	Lines       []*svgLine
	Texts       []*svgText

	completedMerge *myMergeData
	allMerges      map[string]*myMergeData
}

type myMergeData struct {
	moveData []*moveData
	curSize  int
	x0, y0   int
	yn       int
}
type moveData struct {
	arrow       *svgArrow
	dataText    *svgText
	dstPortText *svgText
	yn          int
}

var tmpl = template.Must(template.New("diagram").Parse(svgDiagram))

// FromFlowData creates a SVG diagram from flow data.
// If the flow data isn't valid or the SVG diagram can't be created with its
// template, an error is returned.
func FromFlowData(f *Flow) ([]byte, error) {
	err := validateFlowData(f)
	if err != nil {
		return nil, err
	}

	sf := flowDataToSVGFlow(f)

	return svgFlowToBytes(sf)
}

func validateFlowData(f *Flow) error {
	if f == nil {
		return fmt.Errorf("flow is empty")
	}
	return validateShapes(f.Shapes)
}

func validateShapes(shapes [][]interface{}) error {
	if len(shapes) <= 0 {
		return fmt.Errorf("No shapes found")
	}
	for i, row := range shapes {
		for j, ishape := range row {
			switch shape := ishape.(type) {
			case *Arrow, *Op, *Merge:
				break
			case *Split:
				err := validateShapes(shape.Shapes)
				if err != nil {
					return err
				}
			default:
				return fmt.Errorf(
					"unsupported shape type %T at row index %d and column index %d",
					ishape, i, j)
			}
		}
	}
	return nil
}

func svgFlowToBytes(sf *svgFlow) ([]byte, error) {
	buf := bytes.Buffer{}
	err := tmpl.Execute(&buf, sf)
	if err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func flowDataToSVGFlow(f *Flow) *svgFlow {
	sf, x, y := initSVGData()
	sf, x, y = shapesToSVG(
		f.Shapes,
		sf, x, y,
		arrowDataToSVG,
		opDataToSVG,
		rectDataToSVG,
		splitDataToSVG,
		mergeDataToSVG,
	)
	return adjustDimensions(sf, x, y)
}

func initSVGData() (sf *svgFlow, x0, y0 int) {
	return &svgFlow{
		Arrows: make([]*svgArrow, 0, 64),
		Rects:  make([]*svgRect, 0, 64),
		Lines:  make([]*svgLine, 0, 64),
		Texts:  make([]*svgText, 0, 64),

		allMerges: make(map[string]*myMergeData),
	}, 2, 1
}
func adjustDimensions(sf *svgFlow, xn, yn int) *svgFlow {
	sf.TotalWidth = xn + 2
	sf.TotalHeight = yn + 3
	return sf
}

func shapesToSVG(
	shapes [][]interface{}, sf *svgFlow, x0 int, y0 int,
	pluginArrowDataToSVG func(*Arrow, *svgFlow, int, int) (*svgFlow, int, int, *moveData),
	pluginOpDataToSVG func(*Op, *svgFlow, int, int) (*svgFlow, *svgRect, int, int, int),
	pluginRectDataToSVG func(*Rect, *svgFlow, int, int) (*svgFlow, int, int),
	pluginSplitDataToSVG func(*Split, *svgFlow, *svgRect, int, int) (*svgFlow, int, int),
	pluginMergeDataToSVG func(*Merge, *svgFlow, *moveData, int, int) *myMergeData,
) (nsf *svgFlow, xn, yn int) {
	var xmax, ymax int
	var mod *moveData
	var lsr *svgRect

	for _, ss := range shapes {
		x := x0
		lsr = nil
		if len(ss) < 1 {
			y0 += 48
			continue
		}
		for _, is := range ss {
			y := y0
			switch s := is.(type) {
			case *Arrow:
				sf, x, y, mod = pluginArrowDataToSVG(s, sf, x, y)
				lsr = nil
			case *Op:
				sf, lsr, y0, x, y = pluginOpDataToSVG(s, sf, x, y0)
				sf.completedMerge = nil
			case *Rect:
				sf, x, y = pluginRectDataToSVG(s, sf, x, y)
			case *Split:
				sf, x, y = pluginSplitDataToSVG(s, sf, lsr, x, y)
				lsr = nil
			case *Merge:
				sf.completedMerge = pluginMergeDataToSVG(s, sf, mod, x, y)
				mod = nil
			default:
				panic(fmt.Sprintf("unsupported type: %T", is))
			}

			ymax = max(ymax, y)
		}
		xmax = max(xmax, x)
		y0 = ymax + 5
	}
	return sf, xmax, ymax
}

func min(a, b int) int {
	if a <= b {
		return a
	}
	return b
}
func max(a, b int) int {
	if a >= b {
		return a
	}
	return b
}
<|endoftext|>