<commit_before>// Copyright © 2017 Karl Hepworth <Karl.Hepworth@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 cmd

import (
	"os"

	"fmt"

	"io"
	"strings"

	"github.com/gpmgo/gopm/modules/log"
	"github.com/mitchellh/go-homedir"
	"github.com/spf13/cobra"
)

var (

	// Home is the package variable to store the location of the config files.
	Home string

	// sites_php_template_date is the data for sites.php file.
	// it was taken from the templates folder and serves as a backup
	// when that file isn't available (not a go get install).
	sites_php_template_date = `
<?php

/**
 * @file
 * Configuration file for Drupal's multi-site directory aliasing feature.
 */

 $sites['default'] = '{{ .Name }}';
 $sites['{{ .Alias }}'] = '{{ .Name }}';

?>
`

	// drush_alias_template is the data for drush alias file.
	// it was taken from the templates folder and serves as a backup
	// when that file isn't available (not a go get install).
	drush_alias_template = `
<?php
	$aliases['{{ .Alias }}'] = array(
		'root' => '{{ .Root }}',
		'uri' => '{{ .Domain }}',
		'path-aliases' => array(
			'%files' => 'sites/{{ .Name }}/files',
			'%private' => 'sites/{{ .Name }}/private',
		),
	);
?>
`
	// vhost_template_data is the data for an nginx virtualhost config.
	// nginx is the default configured webserver, so other web server
	// templates will need to be added/changed as required.
	// it was taken from the templates folder and serves as a backup
	// when that file isn't available (not a go get install).
	vhost_template_data = `
server {
    listen 80;

    server_name {{ .Domain }}
    error_log /var/log/nginx/error.log info;
    root {{ .Root }};
    index index.php index.html index.htm;

    location / {
        # Don't touch PHP for static content.
        try_files $uri @rewrite;
    }

    # Don't allow direct access to PHP files in the vendor directory.
    location ~ /vendor/.*\.php$ {
        deny all;
        return 404;
    }

    # Use fastcgi for all php files.
    location ~ \.php$ {
        # Secure *.php files.
        try_files $uri = 404;
        include /etc/nginx/fastcgi_params;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        # fastcgi_pass  127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_read_timeout 120;
    }

    location @rewrite {
        # For D7 and above:
        rewrite ^ /index.php;

        # For Drupal 6 and below:
        #rewrite ^/(.*)$ /index.php?q=$1;
    }

    location ~ ^/sites/.*/files/styles/ {
        try_files $uri @rewrite;
    }

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    location ~ (^|/)\. {
        return 403;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
        expires max;
        log_not_found off;
    }

    gzip on;
    gzip_proxied any;
    gzip_static on;
    gzip_http_version 1.0;
    gzip_disable "MSIE [1-6]\.";
    gzip_vary on;
    gzip_comp_level 6;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/javascript
        application/x-javascript
        application/json
        application/xml
        application/xml+rss
        application/xhtml+xml
        application/x-font-ttf
        application/x-font-opentype
        image/svg+xml
        image/x-icon;
    gzip_buffers 16 8k;
    gzip_min_length 512;
}
`

	// config_yml_template_data is the data for sites.php file.
	// it was taken from the templates folder and serves as a backup
	// when that file isn't available (not a go get install).
	config_yml_template_data = `
---

db_user: drupal
db_pass: drupal
db_host: 127.0.0.1
db_port: 3306

webserver: nginx
alias_template:
sites_php_template:
virtualhost_template:

virtualhost_path: /etc/nginx/sites-enabled/
`
)

func WriteStringToFile(filepath, s string) error {
	fo, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer fo.Close()

	_, err = io.Copy(fo, strings.NewReader(s))
	if err != nil {
		return err
	}

	return nil
}

// syncCmd represents the backup command
var initCmd = &cobra.Command{
	Use:   "init",
	Short: "Initialise a set of templates in the provided destination path",
	Long:  ``,
	Run: func(cmd *cobra.Command, args []string) {

		home, err := homedir.Dir()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		} else {
			Home = home
		}

		r := strings.Join([]string{Home, "yoink", ""}, string(os.PathSeparator))

		e := os.Mkdir(r, 0755)
		if e != nil {
			log.Fatal("error in creating directory", r, ": ", e)
		}

		WriteStringToFile(r+"sites.php.tmpl", sites_php_template_date)
		WriteStringToFile(r+"config.yml", config_yml_template_data)
		WriteStringToFile(r+"alias.tmpl", drush_alias_template)
		WriteStringToFile(r+"vhost.tmpl", vhost_template_data)
		if destination == "" {
			cmd.Usage()
			fmt.Println("\ndestination is not set")
			os.Exit(1)
		}
	},
}

func init() {
	RootCmd.AddCommand(initCmd)
}
<commit_msg>Remove unused import...<commit_after>// Copyright © 2017 Karl Hepworth <Karl.Hepworth@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 cmd

import (
	"log"
	"os"

	"fmt"

	"io"
	"strings"

	"github.com/mitchellh/go-homedir"
	"github.com/spf13/cobra"
)

var (

	// Home is the package variable to store the location of the config files.
	Home string

	// sites_php_template_date is the data for sites.php file.
	// it was taken from the templates folder and serves as a backup
	// when that file isn't available (not a go get install).
	sites_php_template_date = `
<?php

/**
 * @file
 * Configuration file for Drupal's multi-site directory aliasing feature.
 */

 $sites['default'] = '{{ .Name }}';
 $sites['{{ .Alias }}'] = '{{ .Name }}';

?>
`

	// drush_alias_template is the data for drush alias file.
	// it was taken from the templates folder and serves as a backup
	// when that file isn't available (not a go get install).
	drush_alias_template = `
<?php
	$aliases['{{ .Alias }}'] = array(
		'root' => '{{ .Root }}',
		'uri' => '{{ .Domain }}',
		'path-aliases' => array(
			'%files' => 'sites/{{ .Name }}/files',
			'%private' => 'sites/{{ .Name }}/private',
		),
	);
?>
`
	// vhost_template_data is the data for an nginx virtualhost config.
	// nginx is the default configured webserver, so other web server
	// templates will need to be added/changed as required.
	// it was taken from the templates folder and serves as a backup
	// when that file isn't available (not a go get install).
	vhost_template_data = `
server {
    listen 80;

    server_name {{ .Domain }}
    error_log /var/log/nginx/error.log info;
    root {{ .Root }};
    index index.php index.html index.htm;

    location / {
        # Don't touch PHP for static content.
        try_files $uri @rewrite;
    }

    # Don't allow direct access to PHP files in the vendor directory.
    location ~ /vendor/.*\.php$ {
        deny all;
        return 404;
    }

    # Use fastcgi for all php files.
    location ~ \.php$ {
        # Secure *.php files.
        try_files $uri = 404;
        include /etc/nginx/fastcgi_params;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        # fastcgi_pass  127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_read_timeout 120;
    }

    location @rewrite {
        # For D7 and above:
        rewrite ^ /index.php;

        # For Drupal 6 and below:
        #rewrite ^/(.*)$ /index.php?q=$1;
    }

    location ~ ^/sites/.*/files/styles/ {
        try_files $uri @rewrite;
    }

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    location ~ (^|/)\. {
        return 403;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
        expires max;
        log_not_found off;
    }

    gzip on;
    gzip_proxied any;
    gzip_static on;
    gzip_http_version 1.0;
    gzip_disable "MSIE [1-6]\.";
    gzip_vary on;
    gzip_comp_level 6;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/javascript
        application/x-javascript
        application/json
        application/xml
        application/xml+rss
        application/xhtml+xml
        application/x-font-ttf
        application/x-font-opentype
        image/svg+xml
        image/x-icon;
    gzip_buffers 16 8k;
    gzip_min_length 512;
}
`

	// config_yml_template_data is the data for sites.php file.
	// it was taken from the templates folder and serves as a backup
	// when that file isn't available (not a go get install).
	config_yml_template_data = `
---

db_user: drupal
db_pass: drupal
db_host: 127.0.0.1
db_port: 3306

webserver: nginx
alias_template:
sites_php_template:
virtualhost_template:

virtualhost_path: /etc/nginx/sites-enabled/
`
)

func WriteStringToFile(filepath, s string) error {
	fo, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer fo.Close()

	_, err = io.Copy(fo, strings.NewReader(s))
	if err != nil {
		return err
	}

	return nil
}

// syncCmd represents the backup command
var initCmd = &cobra.Command{
	Use:   "init",
	Short: "Initialise a set of templates in the provided destination path",
	Long:  ``,
	Run: func(cmd *cobra.Command, args []string) {

		home, err := homedir.Dir()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		} else {
			Home = home
		}

		r := strings.Join([]string{Home, "yoink", ""}, string(os.PathSeparator))

		e := os.Mkdir(r, 0755)
		if e != nil {
			log.Fatal("error in creating directory", r, ": ", e)
		}

		WriteStringToFile(r+"sites.php.tmpl", sites_php_template_date)
		WriteStringToFile(r+"config.yml", config_yml_template_data)
		WriteStringToFile(r+"alias.tmpl", drush_alias_template)
		WriteStringToFile(r+"vhost.tmpl", vhost_template_data)
		if destination == "" {
			cmd.Usage()
			fmt.Println("\ndestination is not set")
			os.Exit(1)
		}
	},
}

func init() {
	RootCmd.AddCommand(initCmd)
}
<|endoftext|>


<commit_before>package sparta

import (
	"crypto/sha1"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"math/rand"
	"strings"

	"github.com/pkg/errors"
	"github.com/rs/zerolog"
)

// Utility discovery information that is necessary for compilation
// in both local and AWS Binary mode

const (
	nodeColorService     = "#720502"
	nodeColorEventSource = "#BF2803"
	nodeColorAPIGateway  = "#06B5F5"
	nodeNameAPIGateway   = "API Gateway"

	labelWeightNormal = "normal"
	labelWeightBold   = "bolder"
	defaultImagePath  = "/resources/describe/AWS-Architecture-Assets/General@4x.png"
)

// This is the `go` type that's shuttled through the JSON data
// and parsed by the sparta.js script that's executed in the browser
type cytoscapeData struct {
	ID               string `json:"id"`
	Parent           string `json:"parent"`
	Image            string `json:"image"`
	Width            string `json:"width"`
	Height           string `json:"height"`
	BackgroundColor  string `json:"backgroundColor,omitempty"`
	Source           string `json:"source,omitempty"`
	Target           string `json:"target,omitempty"`
	Label            string `json:"label,omitempty"`
	LabelWeight      string `json:"labelWeight,omitempty"`
	DegreeCentrality int    `json:"degreeCentrality"`
}
type cytoscapeNode struct {
	Data    cytoscapeData `json:"data"`
	Classes string        `json:"classes,omitempty"`
}
type templateResource struct {
	KeyName string
	Data    string
}

func cytoscapeNodeID(rawData interface{}) (string, error) {
	bytes, bytesErr := json.Marshal(rawData)
	if bytesErr != nil {
		return "", bytesErr
	}
	hash := sha1.New()
	_, writeErr := hash.Write(bytes)
	if writeErr != nil {
		return "", writeErr
	}
	return hex.EncodeToString(hash.Sum(nil)), nil
}

type descriptionWriter struct {
	nodes  []*cytoscapeNode
	logger *zerolog.Logger
}

func (dw *descriptionWriter) writeNodeWithParent(nodeName string,
	nodeColor string,
	nodeImage string,
	nodeParent string,
	labelWeight string) error {

	nodeID, nodeErr := cytoscapeNodeID(nodeName)
	if nodeErr != nil {
		return errors.Wrapf(nodeErr,
			"Failed to create nodeID for entry: %s",
			nodeName)
	}
	parentID := ""
	if nodeParent != "" {
		tmpParentID, tmpParentIDErr := cytoscapeNodeID(nodeParent)
		if tmpParentIDErr != nil {
			return errors.Wrapf(nodeErr,
				"Failed to create nodeID for entry: %s",
				nodeParent)
		}
		parentID = tmpParentID
	}
	if labelWeight == "" {
		labelWeight = labelWeightNormal
	}
	appendNode := &cytoscapeNode{
		Data: cytoscapeData{
			ID:          nodeID,
			Parent:      parentID,
			Width:       "128",
			Height:      "128",
			Label:       strings.Trim(nodeName, "\""),
			LabelWeight: labelWeight,
		},
	}
	if nodeImage != "" {
		resourceItem := templateResourceForKey(nodeImage, dw.logger)
		if resourceItem != nil {
			appendNode.Data.Image = fmt.Sprintf("data:image/png;base64,%s",
				base64.StdEncoding.EncodeToString([]byte(resourceItem.Data)))
		}
	}
	dw.nodes = append(dw.nodes, appendNode)
	return nil
}

// func (dw *descriptionWriter) writeNode(nodeName string,
// 	nodeColor string,
// 	nodeImage string) error {
// 	return dw.writeNodeWithParent(nodeName, nodeColor, nodeImage, "")
// }

func (dw *descriptionWriter) writeEdge(fromNode string,
	toNode string,
	label string) error {

	nodeSource, nodeSourceErr := cytoscapeNodeID(fromNode)
	if nodeSourceErr != nil {
		return errors.Wrapf(nodeSourceErr,
			"Failed to create nodeID for entry: %s",
			fromNode)
	}
	nodeTarget, nodeTargetErr := cytoscapeNodeID(toNode)
	if nodeTargetErr != nil {
		return errors.Wrapf(nodeSourceErr,
			"Failed to create nodeID for entry: %s",
			toNode)
	}

	dw.nodes = append(dw.nodes, &cytoscapeNode{
		Data: cytoscapeData{
			ID:     fmt.Sprintf("%d", rand.Uint64()),
			Source: nodeSource,
			Target: nodeTarget,
			Label:  label,
		},
	})
	return nil
}

func templateResourceForKey(resourceKeyName string, logger *zerolog.Logger) *templateResource {
	var resource *templateResource
	resourcePath := fmt.Sprintf("resources/describe/%s",
		strings.TrimLeft(resourceKeyName, "/"))

	keyParts := strings.Split(resourcePath, "/")
	keyName := keyParts[len(keyParts)-1]

	data, dataErr := embeddedString(resourcePath)
	if dataErr == nil {
		resource = &templateResource{
			KeyName: keyName,
			Data:    data,
		}
		logger.Debug().
			Str("Path", resourcePath).
			Str("KeyName", keyName).
			Msg("Embedded resource")

	} else {
		logger.Warn().
			Str("Path", resourcePath).
			Msg("Failed to find resource. Using default image.")

		data, dataErr = embeddedString(defaultImagePath)
		if dataErr == nil {
			resource = &templateResource{
				KeyName: keyName,
				Data:    data,
			}
		} else {
			logger.Error().
				Str("Path", defaultImagePath).
				Err(dataErr).
				Msg("Failed to find default resource")
		}
	}
	return resource
}

func templateResourcesForKeys(resourceKeyNames []string, logger *zerolog.Logger) []*templateResource {
	var resources []*templateResource

	for _, eachKey := range resourceKeyNames {
		loadedResource := templateResourceForKey(eachKey, logger)
		if loadedResource != nil {
			resources = append(resources, loadedResource)
		}
	}
	return resources
}

func templateCSSFiles(logger *zerolog.Logger) []*templateResource {
	cssFiles := []string{"bootstrap-4.0.0/dist/css/bootstrap.min.css",
		"highlight.js/styles/xcode.css",
	}
	return templateResourcesForKeys(cssFiles, logger)
}

func templateJSFiles(logger *zerolog.Logger) []*templateResource {
	jsFiles := []string{"jquery/jquery-3.3.1.min.js",
		"popper/popper.min.js",
		"bootstrap-4.0.0/dist/js/bootstrap.min.js",
		"highlight.js/highlight.pack.js",
		"dagre-0.8.4/dist/dagre.min.js",
		"cytoscape.js/dist/cytoscape.min.js",
		"cytoscape.js-dagre/cytoscape-dagre.js",
		"sparta.js",
	}
	return templateResourcesForKeys(jsFiles, logger)
}

func templateImageMap(logger *zerolog.Logger) map[string]string {
	images := []string{"SpartaHelmet256.png",
		"AWS-Architecture-Assets/Res_Compute/Res_48_Light/Res_Amazon-Lambda_Lambda-Function_48_Light.png",
		"AWS-Architecture-Assets/Arch_Management-Governance/64/Arch_AWS-CloudFormation_64@5x.png",
	}
	resources := templateResourcesForKeys(images, logger)
	imageMap := make(map[string]string)
	for _, eachResource := range resources {
		imageMap[eachResource.KeyName] = base64.StdEncoding.EncodeToString([]byte(eachResource.Data))
	}
	return imageMap
}

// TODO - this should really be smarter, including
// looking at the referred resource to understand it's
// type
func iconForAWSResource(rawEmitter interface{}) *DescriptionIcon {
	jsonBytes, jsonBytesErr := json.Marshal(rawEmitter)
	if jsonBytesErr != nil {
		jsonBytes = make([]byte, 0)
	}

	canonicalRaw := strings.ToLower(string(jsonBytes))
	iconMappings := map[string]*DescriptionIcon{
		"dynamodb": {
			Category: "Arch_Database",
			Name:     "64/Arch_Amazon-DynamoDB_64@5x.png",
		},
		"sqs": {
			Category: "Arch_App-Integration",
			Name:     "Arch_64/Arch_AWS-Simple-Queue-Service_64@5x.png",
		},
		"sns": {
			Category: "Arch_App-Integration",
			Name:     "Arch_64/Arch_AWS-Simple-Notification-Service_64@5x.png",
		},
		"step": {
			Category: "Arch_App-Integration",
			Name:     "Arch_64/Arch_AWS-Step-Functions_64@5x.png",
		},
		"cloudwatch": {
			Category: "Arch_Management-Governance",
			Name:     "64/Arch_Amazon-CloudWatch_64@5x.png",
		},
		"kinesis": {
			Category: "Arch_Analytics",
			Name:     "Arch_64/Arch_Amazon-Kinesis_64@5x.png",
		},
		"s3": {
			Category: "Arch_Storage",
			Name:     "64/Arch_Amazon-S3-Standard_64@5x.png",
		},
		"codecommit": {
			Category: "Arch_Developer- Tools",
			Name:     "64/Arch_AWS-CodeCommit_64@5x.png",
		},
	}
	// Return it if we have it...
	for eachKey, eachIcon := range iconMappings {
		if strings.Contains(canonicalRaw, eachKey) {
			return eachIcon
		}
	}
	return &DescriptionIcon{
		Category: "Default",
		Name:     "General@4x.png",
	}
}

// DescriptionIcon is the struct that contains the category & icon
// to use when rendering a ndoe
type DescriptionIcon struct {
	Category string
	Name     string
}

// DescriptionDisplayInfo encapsulates information that is for display only
type DescriptionDisplayInfo struct {
	SourceNodeColor string
	SourceIcon      *DescriptionIcon
}

// DescriptionTriplet is a node that should be included in the final
// describe output.
type DescriptionTriplet struct {
	SourceNodeName string
	ArcLabel       string
	DisplayInfo    *DescriptionDisplayInfo
	TargetNodeName string
}

// DescriptionInfo is the set of information that represents a DescribeableResource
type DescriptionInfo struct {
	Name  string
	Nodes []*DescriptionTriplet
}

// Describable represents the interface for something that can
// provide a description
type Describable interface {
	Describe(targetNodeName string) (*DescriptionInfo, error)
}
<commit_msg>Comment for future self<commit_after>package sparta

import (
	"crypto/sha1"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"math/rand"
	"strings"

	"github.com/pkg/errors"
	"github.com/rs/zerolog"
)

// Utility discovery information that is necessary for compilation
// in both local and AWS Binary mode

const (
	nodeColorService     = "#720502"
	nodeColorEventSource = "#BF2803"
	nodeColorAPIGateway  = "#06B5F5"
	nodeNameAPIGateway   = "API Gateway"

	labelWeightNormal = "normal"
	labelWeightBold   = "bolder"
	defaultImagePath  = "/resources/describe/AWS-Architecture-Assets/General@4x.png"
)

// This is the `go` type that's shuttled through the JSON data
// and parsed by the sparta.js script that's executed in the browser
type cytoscapeData struct {
	ID               string `json:"id"`
	Parent           string `json:"parent"`
	Image            string `json:"image"`
	Width            string `json:"width"`
	Height           string `json:"height"`
	BackgroundColor  string `json:"backgroundColor,omitempty"`
	Source           string `json:"source,omitempty"`
	Target           string `json:"target,omitempty"`
	Label            string `json:"label,omitempty"`
	LabelWeight      string `json:"labelWeight,omitempty"`
	DegreeCentrality int    `json:"degreeCentrality"`
}
type cytoscapeNode struct {
	Data    cytoscapeData `json:"data"`
	Classes string        `json:"classes,omitempty"`
}
type templateResource struct {
	KeyName string
	Data    string
}

func cytoscapeNodeID(rawData interface{}) (string, error) {
	bytes, bytesErr := json.Marshal(rawData)
	if bytesErr != nil {
		return "", bytesErr
	}
	hash := sha1.New()
	_, writeErr := hash.Write(bytes)
	if writeErr != nil {
		return "", writeErr
	}
	return hex.EncodeToString(hash.Sum(nil)), nil
}

type descriptionWriter struct {
	nodes  []*cytoscapeNode
	logger *zerolog.Logger
}

func (dw *descriptionWriter) writeNodeWithParent(nodeName string,
	nodeColor string,
	nodeImage string,
	nodeParent string,
	labelWeight string) error {

	nodeID, nodeErr := cytoscapeNodeID(nodeName)
	if nodeErr != nil {
		return errors.Wrapf(nodeErr,
			"Failed to create nodeID for entry: %s",
			nodeName)
	}
	parentID := ""
	if nodeParent != "" {
		tmpParentID, tmpParentIDErr := cytoscapeNodeID(nodeParent)
		if tmpParentIDErr != nil {
			return errors.Wrapf(nodeErr,
				"Failed to create nodeID for entry: %s",
				nodeParent)
		}
		parentID = tmpParentID
	}
	if labelWeight == "" {
		labelWeight = labelWeightNormal
	}
	appendNode := &cytoscapeNode{
		Data: cytoscapeData{
			ID:          nodeID,
			Parent:      parentID,
			Width:       "128",
			Height:      "128",
			Label:       strings.Trim(nodeName, "\""),
			LabelWeight: labelWeight,
		},
	}
	if nodeImage != "" {
		resourceItem := templateResourceForKey(nodeImage, dw.logger)
		if resourceItem != nil {
			appendNode.Data.Image = fmt.Sprintf("data:image/png;base64,%s",
				base64.StdEncoding.EncodeToString([]byte(resourceItem.Data)))
		}
	}
	dw.nodes = append(dw.nodes, appendNode)
	return nil
}

// func (dw *descriptionWriter) writeNode(nodeName string,
// 	nodeColor string,
// 	nodeImage string) error {
// 	return dw.writeNodeWithParent(nodeName, nodeColor, nodeImage, "")
// }

func (dw *descriptionWriter) writeEdge(fromNode string,
	toNode string,
	label string) error {

	nodeSource, nodeSourceErr := cytoscapeNodeID(fromNode)
	if nodeSourceErr != nil {
		return errors.Wrapf(nodeSourceErr,
			"Failed to create nodeID for entry: %s",
			fromNode)
	}
	nodeTarget, nodeTargetErr := cytoscapeNodeID(toNode)
	if nodeTargetErr != nil {
		return errors.Wrapf(nodeSourceErr,
			"Failed to create nodeID for entry: %s",
			toNode)
	}

	dw.nodes = append(dw.nodes, &cytoscapeNode{
		Data: cytoscapeData{
			ID:     fmt.Sprintf("%d", rand.Uint64()),
			Source: nodeSource,
			Target: nodeTarget,
			Label:  label,
		},
	})
	return nil
}

func templateResourceForKey(resourceKeyName string, logger *zerolog.Logger) *templateResource {
	var resource *templateResource
	resourcePath := fmt.Sprintf("resources/describe/%s",
		strings.TrimLeft(resourceKeyName, "/"))

	keyParts := strings.Split(resourcePath, "/")
	keyName := keyParts[len(keyParts)-1]

	data, dataErr := embeddedString(resourcePath)
	if dataErr == nil {
		resource = &templateResource{
			KeyName: keyName,
			Data:    data,
		}
		logger.Debug().
			Str("Path", resourcePath).
			Str("KeyName", keyName).
			Msg("Embedded resource")

	} else {
		logger.Warn().
			Str("Path", resourcePath).
			Msg("Failed to find resource. Using default image.")

		data, dataErr = embeddedString(defaultImagePath)
		if dataErr == nil {
			resource = &templateResource{
				KeyName: keyName,
				Data:    data,
			}
		} else {
			logger.Error().
				Str("Path", defaultImagePath).
				Err(dataErr).
				Msg("Failed to find default resource")
		}
	}
	return resource
}

func templateResourcesForKeys(resourceKeyNames []string, logger *zerolog.Logger) []*templateResource {
	var resources []*templateResource

	for _, eachKey := range resourceKeyNames {
		loadedResource := templateResourceForKey(eachKey, logger)
		if loadedResource != nil {
			resources = append(resources, loadedResource)
		}
	}
	return resources
}

func templateCSSFiles(logger *zerolog.Logger) []*templateResource {
	cssFiles := []string{"bootstrap-4.0.0/dist/css/bootstrap.min.css",
		"highlight.js/styles/xcode.css",
	}
	return templateResourcesForKeys(cssFiles, logger)
}

func templateJSFiles(logger *zerolog.Logger) []*templateResource {
	jsFiles := []string{"jquery/jquery-3.3.1.min.js",
		"popper/popper.min.js",
		"bootstrap-4.0.0/dist/js/bootstrap.min.js",
		"highlight.js/highlight.pack.js",
		"dagre-0.8.4/dist/dagre.min.js",
		"cytoscape.js/dist/cytoscape.min.js",
		"cytoscape.js-dagre/cytoscape-dagre.js",
		"sparta.js",
	}
	return templateResourcesForKeys(jsFiles, logger)
}

func templateImageMap(logger *zerolog.Logger) map[string]string {
	images := []string{"SpartaHelmet256.png",
		"AWS-Architecture-Assets/Res_Compute/Res_48_Light/Res_Amazon-Lambda_Lambda-Function_48_Light.png",
		"AWS-Architecture-Assets/Arch_Management-Governance/64/Arch_AWS-CloudFormation_64@5x.png",
	}
	resources := templateResourcesForKeys(images, logger)
	imageMap := make(map[string]string)
	for _, eachResource := range resources {
		imageMap[eachResource.KeyName] = base64.StdEncoding.EncodeToString([]byte(eachResource.Data))
	}
	return imageMap
}

// TODO - this should really be smarter, including
// looking at the referred resource to understand it's
// type
func iconForAWSResource(rawEmitter interface{}) *DescriptionIcon {
	jsonBytes, jsonBytesErr := json.Marshal(rawEmitter)
	if jsonBytesErr != nil {
		jsonBytes = make([]byte, 0)
	}
	// TODO - it's possible to resolve the type and pick the
	// proper icon. To do that, we'd need to call resolveRef,
	// then lookup the item.
	canonicalRaw := strings.ToLower(string(jsonBytes))
	iconMappings := map[string]*DescriptionIcon{
		"dynamodb": {
			Category: "Arch_Database",
			Name:     "64/Arch_Amazon-DynamoDB_64@5x.png",
		},
		"sqs": {
			Category: "Arch_App-Integration",
			Name:     "Arch_64/Arch_AWS-Simple-Queue-Service_64@5x.png",
		},
		"sns": {
			Category: "Arch_App-Integration",
			Name:     "Arch_64/Arch_AWS-Simple-Notification-Service_64@5x.png",
		},
		"step": {
			Category: "Arch_App-Integration",
			Name:     "Arch_64/Arch_AWS-Step-Functions_64@5x.png",
		},
		"cloudwatch": {
			Category: "Arch_Management-Governance",
			Name:     "64/Arch_Amazon-CloudWatch_64@5x.png",
		},
		"kinesis": {
			Category: "Arch_Analytics",
			Name:     "Arch_64/Arch_Amazon-Kinesis_64@5x.png",
		},
		"s3": {
			Category: "Arch_Storage",
			Name:     "64/Arch_Amazon-S3-Standard_64@5x.png",
		},
		"codecommit": {
			Category: "Arch_Developer- Tools",
			Name:     "64/Arch_AWS-CodeCommit_64@5x.png",
		},
	}
	// Return it if we have it...
	for eachKey, eachIcon := range iconMappings {
		if strings.Contains(canonicalRaw, eachKey) {
			return eachIcon
		}
	}
	return &DescriptionIcon{
		Category: "Default",
		Name:     "General@4x.png",
	}
}

// DescriptionIcon is the struct that contains the category & icon
// to use when rendering a ndoe
type DescriptionIcon struct {
	Category string
	Name     string
}

// DescriptionDisplayInfo encapsulates information that is for display only
type DescriptionDisplayInfo struct {
	SourceNodeColor string
	SourceIcon      *DescriptionIcon
}

// DescriptionTriplet is a node that should be included in the final
// describe output.
type DescriptionTriplet struct {
	SourceNodeName string
	ArcLabel       string
	DisplayInfo    *DescriptionDisplayInfo
	TargetNodeName string
}

// DescriptionInfo is the set of information that represents a DescribeableResource
type DescriptionInfo struct {
	Name  string
	Nodes []*DescriptionTriplet
}

// Describable represents the interface for something that can
// provide a description
type Describable interface {
	Describe(targetNodeName string) (*DescriptionInfo, error)
}
<|endoftext|>


<commit_before>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 oglematchers_test

import (
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
)

////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////

type IdenticalToTest struct {
}

func init() { RegisterTestSuite(&IdenticalToTest{}) }

////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////

func (t *IdenticalToTest) TypesNotIdentical() {
	var m Matcher
	var err error

	type intAlias int

	// Type alias expected value
	m = IdenticalTo(intAlias(17))
	err = m.Matches(int(17))
	ExpectThat(err, Error(Equals("which is of type int")))

	// Type alias candidate
	m = IdenticalTo(int(17))
	err = m.Matches(intAlias(17))
	ExpectThat(err, Error(Equals("which is of type intAlias")))

	// int and uint
	m = IdenticalTo(int(17))
	err = m.Matches(uint(17))
	ExpectThat(err, Error(Equals("which is of type uint")))
}

func (t *IdenticalToTest) InvalidTypeExpectedValue() {
	f := func() { IdenticalTo(nil) }
	ExpectThat(f, Panics(AllOf(HasSubstr("IdenticalTo"), HasSubstr("invalid"))))
}

func (t *IdenticalToTest) InvalidTypeCandidate() {
	var m Matcher
	var err error

	// Nil chan expected value
	m = IdenticalTo((chan int)(nil))
	err = m.Matches(nil)
	ExpectThat(err, Error(Equals("which is of type <nil>")))

	// Non-nil chan expected value
	m = IdenticalTo(make(chan int))
	err = m.Matches(nil)
	ExpectThat(err, Error(Equals("which is of type <nil>")))
}

func (t *IdenticalToTest) Slices() {
	var m Matcher
	var err error

	// Nil expected value
	m = IdenticalTo(([]int)(nil))

	err = m.Matches(([]int)(nil))
	ExpectEq(nil, err)

	err = m.Matches([]int{})
	ExpectThat(err, Equals("which is not an identical reference"))

	// Non-nil expected value
	o1 := []int{}
	o2 := []int{}
	m = IdenticalTo(o1)

	err = m.Matches(o1)
	ExpectEq(nil, err)

	err = m.Matches(o2)
	ExpectThat(err, Equals("which is not an identical reference"))
}

func (t *IdenticalToTest) Maps() {
	var m Matcher
	var err error

	// Nil expected value
	m = IdenticalTo((map[int]int)(nil))

	err = m.Matches((map[int]int)(nil))
	ExpectEq(nil, err)

	err = m.Matches(map[int]int{})
	ExpectThat(err, Equals("which is not an identical reference"))

	// Non-nil expected value
	o1 := map[int]int{}
	o2 := map[int]int{}
	m = IdenticalTo(o1)

	err = m.Matches(o1)
	ExpectEq(nil, err)

	err = m.Matches(o2)
	ExpectThat(err, Equals("which is not an identical reference"))
}

func (t *IdenticalToTest) Functions() {
	var m Matcher
	var err error

	// Nil expected value
	m = IdenticalTo((func())(nil))

	err = m.Matches((func())(nil))
	ExpectEq(nil, err)

	err = m.Matches(func(){})
	ExpectThat(err, Equals("which is not an identical reference"))

	// Non-nil expected value
	o1 := func() {}
	o2 := func() {}
	m = IdenticalTo(o1)

	err = m.Matches(o1)
	ExpectEq(nil, err)

	err = m.Matches(o2)
	ExpectThat(err, Equals("which is not an identical reference"))
}

func (t *IdenticalToTest) Channels() {
	var m Matcher
	var err error

	// Nil expected value
	m = IdenticalTo((chan int)(nil))

	err = m.Matches((chan int)(nil))
	ExpectEq(nil, err)

	err = m.Matches(make(chan int))
	ExpectThat(err, Equals("which is not an identical reference"))

	// Non-nil expected value
	o1 := make(chan int)
	o2 := make(chan int)
	m = IdenticalTo(o1)

	err = m.Matches(o1)
	ExpectEq(nil, err)

	err = m.Matches(o2)
	ExpectThat(err, Equals("which is not an identical reference"))
}

func (t *IdenticalToTest) Bools() {
	var m Matcher
	var err error

	// false
	m = IdenticalTo(false)

	err = m.Matches(false)
	ExpectEq(nil, err)

	err = m.Matches(true)
	ExpectThat(err, Error(Equals("")))

	// true
	m = IdenticalTo(true)

	err = m.Matches(false)
	ExpectThat(err, Error(Equals("")))

	err = m.Matches(true)
	ExpectEq(nil, err)
}

func (t *IdenticalToTest) Ints() {
}

func (t *IdenticalToTest) Int8s() {
}

func (t *IdenticalToTest) Int16s() {
}

func (t *IdenticalToTest) Int32s() {
}

func (t *IdenticalToTest) Int64s() {
}

func (t *IdenticalToTest) Uints() {
}

func (t *IdenticalToTest) Uint8s() {
}

func (t *IdenticalToTest) Uint16s() {
}

func (t *IdenticalToTest) Uint32s() {
}

func (t *IdenticalToTest) Uint64s() {
}

func (t *IdenticalToTest) Float32s() {
}

func (t *IdenticalToTest) Float64s() {
}

func (t *IdenticalToTest) Complex64s() {
}

func (t *IdenticalToTest) Complex128s() {
}

func (t *IdenticalToTest) ComparableArrays() {
}

func (t *IdenticalToTest) NonComparableArrays() {
}

func (t *IdenticalToTest) ComparableInterfaces() {
}

func (t *IdenticalToTest) NonComparableInterfaces() {
}

func (t *IdenticalToTest) Pointers() {
}

func (t *IdenticalToTest) Strings() {
}

func (t *IdenticalToTest) ComparableStructs() {
}

func (t *IdenticalToTest) NonComparableStructs() {
}

func (t *IdenticalToTest) UnsafePointers() {
}

func (t *IdenticalToTest) IntAlias() {
}
<commit_msg>Added description tests.<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 oglematchers_test

import (
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"fmt"
)

////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////

type IdenticalToTest struct {
}

func init() { RegisterTestSuite(&IdenticalToTest{}) }

////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////

func (t *IdenticalToTest) TypesNotIdentical() {
	var m Matcher
	var err error

	type intAlias int

	// Type alias expected value
	m = IdenticalTo(intAlias(17))
	err = m.Matches(int(17))
	ExpectThat(err, Error(Equals("which is of type int")))

	// Type alias candidate
	m = IdenticalTo(int(17))
	err = m.Matches(intAlias(17))
	ExpectThat(err, Error(Equals("which is of type intAlias")))

	// int and uint
	m = IdenticalTo(int(17))
	err = m.Matches(uint(17))
	ExpectThat(err, Error(Equals("which is of type uint")))
}

func (t *IdenticalToTest) InvalidTypeExpectedValue() {
	f := func() { IdenticalTo(nil) }
	ExpectThat(f, Panics(AllOf(HasSubstr("IdenticalTo"), HasSubstr("invalid"))))
}

func (t *IdenticalToTest) InvalidTypeCandidate() {
	var m Matcher
	var err error

	// Nil chan expected value
	m = IdenticalTo((chan int)(nil))
	err = m.Matches(nil)
	ExpectThat(err, Error(Equals("which is of type <nil>")))

	// Non-nil chan expected value
	m = IdenticalTo(make(chan int))
	err = m.Matches(nil)
	ExpectThat(err, Error(Equals("which is of type <nil>")))
}

func (t *IdenticalToTest) Slices() {
	var m Matcher
	var err error

	// Nil expected value
	m = IdenticalTo(([]int)(nil))
	ExpectEq("identical to <[]int> nil", m.Description())

	err = m.Matches(([]int)(nil))
	ExpectEq(nil, err)

	err = m.Matches([]int{})
	ExpectThat(err, Equals("which is not an identical reference"))

	// Non-nil expected value
	o1 := []int{}
	o2 := []int{}
	m = IdenticalTo(o1)
	ExpectEq(fmt.Sprintf("identical to <[]int> %v", o1), m.Description())

	err = m.Matches(o1)
	ExpectEq(nil, err)

	err = m.Matches(o2)
	ExpectThat(err, Equals("which is not an identical reference"))
}

func (t *IdenticalToTest) Maps() {
	var m Matcher
	var err error

	// Nil expected value
	m = IdenticalTo((map[int]int)(nil))
	ExpectEq("identical to <map[int]int> nil", m.Description())

	err = m.Matches((map[int]int)(nil))
	ExpectEq(nil, err)

	err = m.Matches(map[int]int{})
	ExpectThat(err, Equals("which is not an identical reference"))

	// Non-nil expected value
	o1 := map[int]int{}
	o2 := map[int]int{}
	m = IdenticalTo(o1)
	ExpectEq(fmt.Sprintf("identical to <map[int]int> %v", o1), m.Description())

	err = m.Matches(o1)
	ExpectEq(nil, err)

	err = m.Matches(o2)
	ExpectThat(err, Equals("which is not an identical reference"))
}

func (t *IdenticalToTest) Functions() {
	var m Matcher
	var err error

	// Nil expected value
	m = IdenticalTo((func())(nil))
	ExpectEq("identical to <func()> nil", m.Description())

	err = m.Matches((func())(nil))
	ExpectEq(nil, err)

	err = m.Matches(func(){})
	ExpectThat(err, Equals("which is not an identical reference"))

	// Non-nil expected value
	o1 := func() {}
	o2 := func() {}
	m = IdenticalTo(o1)
	ExpectEq(fmt.Sprintf("identical to <func()> %v", o1), m.Description())

	err = m.Matches(o1)
	ExpectEq(nil, err)

	err = m.Matches(o2)
	ExpectThat(err, Equals("which is not an identical reference"))
}

func (t *IdenticalToTest) Channels() {
	var m Matcher
	var err error

	// Nil expected value
	m = IdenticalTo((chan int)(nil))
	ExpectEq("identical to <chan int> nil", m.Description())

	err = m.Matches((chan int)(nil))
	ExpectEq(nil, err)

	err = m.Matches(make(chan int))
	ExpectThat(err, Equals("which is not an identical reference"))

	// Non-nil expected value
	o1 := make(chan int)
	o2 := make(chan int)
	m = IdenticalTo(o1)
	ExpectEq(fmt.Sprintf("identical to <chan int> %v", o1), m.Description())

	err = m.Matches(o1)
	ExpectEq(nil, err)

	err = m.Matches(o2)
	ExpectThat(err, Equals("which is not an identical reference"))
}

func (t *IdenticalToTest) Bools() {
	var m Matcher
	var err error

	// false
	m = IdenticalTo(false)
	ExpectEq("identical to <bool> false", m.Description())

	err = m.Matches(false)
	ExpectEq(nil, err)

	err = m.Matches(true)
	ExpectThat(err, Error(Equals("")))

	// true
	m = IdenticalTo(true)
	ExpectEq("identical to <bool> true", m.Description())

	err = m.Matches(false)
	ExpectThat(err, Error(Equals("")))

	err = m.Matches(true)
	ExpectEq(nil, err)
}

func (t *IdenticalToTest) Ints() {
}

func (t *IdenticalToTest) Int8s() {
}

func (t *IdenticalToTest) Int16s() {
}

func (t *IdenticalToTest) Int32s() {
}

func (t *IdenticalToTest) Int64s() {
}

func (t *IdenticalToTest) Uints() {
}

func (t *IdenticalToTest) Uint8s() {
}

func (t *IdenticalToTest) Uint16s() {
}

func (t *IdenticalToTest) Uint32s() {
}

func (t *IdenticalToTest) Uint64s() {
}

func (t *IdenticalToTest) Float32s() {
}

func (t *IdenticalToTest) Float64s() {
}

func (t *IdenticalToTest) Complex64s() {
}

func (t *IdenticalToTest) Complex128s() {
}

func (t *IdenticalToTest) ComparableArrays() {
}

func (t *IdenticalToTest) NonComparableArrays() {
}

func (t *IdenticalToTest) ComparableInterfaces() {
}

func (t *IdenticalToTest) NonComparableInterfaces() {
}

func (t *IdenticalToTest) Pointers() {
}

func (t *IdenticalToTest) Strings() {
}

func (t *IdenticalToTest) ComparableStructs() {
}

func (t *IdenticalToTest) NonComparableStructs() {
}

func (t *IdenticalToTest) UnsafePointers() {
}

func (t *IdenticalToTest) IntAlias() {
}
<|endoftext|>


<commit_before>package codebase

import (
    "errors"
    "fmt"
    "log"
    "sort"
    "strings"
    "time"
)

const (
    timeLayout = "2006-01-02"
)

type milestoneArray struct {
    Milestones []Milestone `xml:"ticketing-milestone"`
}

type Milestone struct {
    Id            int    `xml:"id"`
    Name          string `xml:"name"`
    StartAt       string `xml:"start-at"`
    Deadline      string `xml:"deadline"`
    Status        string `xml:"status"`
    Description   string `xml:"description"`
    EstimatedTime int64  `xml:"estimated-time"`
}

type byMostRecent struct {
    ms []Milestone
}

func (m byMostRecent) Len() int           { return len(m.ms) }
func (m byMostRecent) Swap(i, j int)      { m.ms[i], m.ms[j] = m.ms[j], m.ms[i] }
func (m byMostRecent) Less(i, j int) bool { return m.ms[i].toEadges() < m.ms[j].toEadges() }

func (m *Milestone) IsActive() bool {
    return m.Status == "active"
}

func (m *Milestone) StartAtTime() (t time.Time, err error) {
    if m.StartAt != "" {
        t, err = time.Parse(timeLayout, m.StartAt)
    } else {
        err = errors.New("Start time not available")
    }
    return
}

func (m *Milestone) DeadlineTime() (t time.Time, err error) {
    if m.Deadline != "" {
        t, err = time.Parse(timeLayout, m.Deadline)
    } else {
        err = errors.New("Deadline not available")
    }
    return
}

func (m *Milestone) toEadges() float64 {
    start_date := time.Unix(0, 0)
    end_date := time.Unix(0, 0)
    if m.StartAt != "" {
        start_date, _ = time.Parse(timeLayout, m.StartAt)
    }
    if m.Deadline != "" {
        end_date, _ = time.Parse(timeLayout, m.Deadline)
    }

    to_start := time.Since(start_date).Hours()
    to_end := time.Since(end_date).Hours()

    return to_start + to_end
}

func (m *Milestone) DaysToEnd() (int64, error) {
    end_date, err := m.DeadlineTime()
    if err != nil {
        return 0, err
    }

    days := WorkingDays(end_date).SinceUnix() - WorkingDays(time.Now()).SinceUnix()
    return days, nil
}

func (m *Milestone) DaysToEndStr() string {
    daysToEnd, err := m.DaysToEnd()
    if err != nil {
        return ""
    }

    if daysToEnd == 0 {
        return "Ends today"
    }

    ending := ""
    if daysToEnd < -1 || 1 < daysToEnd {
        ending = "s"
    }

    if daysToEnd > 0 {
        return fmt.Sprintf("Ends in %v day%v", daysToEnd, ending)
    } else {
        return fmt.Sprintf("Ended %v day%v ago", -daysToEnd, ending)
    }
}

func (c *CodeBaseAPI) Milesones() (milestones []Milestone) {
    queryOpts := baseQueryOptions{}

    proxy := milestoneArray{}
    if err := c.fetchFromCodebase("milestones", &proxy, queryOpts); err != nil {
        log.Fatalln("Could not fetch milestones:", err)
    }

    milestones = proxy.Milestones
    return
}

func (c *CodeBaseAPI) CurrentMilestone() (current Milestone) {
    allMilestones := c.Milesones()

    var activeMilestones []Milestone
    for _, milestone := range allMilestones {
        if !milestone.IsActive() {
            continue
        }

        activeMilestones = append(activeMilestones, milestone)
    }

    sort.Reverse(byMostRecent{activeMilestones})
    current = activeMilestones[0]

    return
}

func (m Milestone) String() string {
    daysToEnd := m.DaysToEndStr()
    if daysToEnd != "" {
        return fmt.Sprintf("%v (%v)", m.Name, strings.ToLower(daysToEnd))
    }

    return m.Name
}

func (m *Milestone) EstimatedTimeDuration() time.Duration {
    if m.EstimatedTime <= 0 {
        return 0
    }

    return time.Duration(m.EstimatedTime) * time.Minute
}
<commit_msg>Calcualte numbers of working days in sprint<commit_after>package codebase

import (
    "errors"
    "fmt"
    "log"
    "sort"
    "strings"
    "time"
)

const (
    timeLayout = "2006-01-02"
)

type milestoneArray struct {
    Milestones []Milestone `xml:"ticketing-milestone"`
}

type Milestone struct {
    Id            int    `xml:"id"`
    Name          string `xml:"name"`
    StartAt       string `xml:"start-at"`
    Deadline      string `xml:"deadline"`
    Status        string `xml:"status"`
    Description   string `xml:"description"`
    EstimatedTime int64  `xml:"estimated-time"`
}

type byMostRecent struct {
    ms []Milestone
}

func (m byMostRecent) Len() int           { return len(m.ms) }
func (m byMostRecent) Swap(i, j int)      { m.ms[i], m.ms[j] = m.ms[j], m.ms[i] }
func (m byMostRecent) Less(i, j int) bool { return m.ms[i].toEadges() < m.ms[j].toEadges() }

func (m *Milestone) IsActive() bool {
    return m.Status == "active"
}

func (m *Milestone) StartAtTime() (t time.Time, err error) {
    if m.StartAt != "" {
        t, err = time.Parse(timeLayout, m.StartAt)
    } else {
        err = errors.New("Start time not available")
    }
    return
}

func (m *Milestone) DeadlineTime() (t time.Time, err error) {
    if m.Deadline != "" {
        t, err = time.Parse(timeLayout, m.Deadline)
    } else {
        err = errors.New("Deadline not available")
    }
    return
}

func (m *Milestone) toEadges() float64 {
    start_date := time.Unix(0, 0)
    end_date := time.Unix(0, 0)
    if m.StartAt != "" {
        start_date, _ = time.Parse(timeLayout, m.StartAt)
    }
    if m.Deadline != "" {
        end_date, _ = time.Parse(timeLayout, m.Deadline)
    }

    to_start := time.Since(start_date).Hours()
    to_end := time.Since(end_date).Hours()

    return to_start + to_end
}

func (m *Milestone) DaysToEnd() (int64, error) {
    end_date, err := m.DeadlineTime()
    if err != nil {
        return 0, err
    }

    days := WorkingDays(end_date).SinceUnix() - WorkingDays(time.Now()).SinceUnix()
    return days, nil
}

func (m *Milestone) DaysToEndStr() string {
    daysToEnd, err := m.DaysToEnd()
    if err != nil {
        return ""
    }

    if daysToEnd == 0 {
        return "Ends today"
    }

    ending := ""
    if daysToEnd < -1 || 1 < daysToEnd {
        ending = "s"
    }

    if daysToEnd > 0 {
        return fmt.Sprintf("Ends in %v day%v", daysToEnd, ending)
    } else {
        return fmt.Sprintf("Ended %v day%v ago", -daysToEnd, ending)
    }
}

func (m *Milestone) WorkingDaysInSprint() (int64, error) {
    start, err := m.StartAtTime()
    if err != nil {
        return 0, err
    }

    end, err := m.DeadlineTime()
    if err != nil {
        return 0, err
    }

    days := WorkingDays(end).SinceUnix() - WorkingDays(start).SinceUnix()

    return days, nil
}

func (c *CodeBaseAPI) Milesones() (milestones []Milestone) {
    queryOpts := baseQueryOptions{}

    proxy := milestoneArray{}
    if err := c.fetchFromCodebase("milestones", &proxy, queryOpts); err != nil {
        log.Fatalln("Could not fetch milestones:", err)
    }

    milestones = proxy.Milestones
    return
}

func (c *CodeBaseAPI) CurrentMilestone() (current Milestone) {
    allMilestones := c.Milesones()

    var activeMilestones []Milestone
    for _, milestone := range allMilestones {
        if !milestone.IsActive() {
            continue
        }

        activeMilestones = append(activeMilestones, milestone)
    }

    sort.Reverse(byMostRecent{activeMilestones})
    current = activeMilestones[0]

    return
}

func (m Milestone) String() string {
    daysToEnd := m.DaysToEndStr()
    if daysToEnd != "" {
        return fmt.Sprintf("%v (%v)", m.Name, strings.ToLower(daysToEnd))
    }

    return m.Name
}

func (m *Milestone) EstimatedTimeDuration() time.Duration {
    if m.EstimatedTime <= 0 {
        return 0
    }

    return time.Duration(m.EstimatedTime) * time.Minute
}
<|endoftext|>


<commit_before>// In cases where Go's garbage collecter is unresponsive, or even broken, this
// allows you to easily manage blocks of memory.  This can be useful if you
// anticipate churning through lots of memory very quickly.  This package
// creates a default Manager that can be used through the package functions,
// but you can create your own Manager if you want to eventually return the
// memory back to Go.
package memory

import (
  "fmt"
  "sync"
)

// We don't bother allocating blocks smaller than this.  This choice is
// somewhat arbitrary, but should probably be appropriate for most
// applications that want to manage their own memory.
const smallestBlockSize = 1024

// A Manager allocates memory as necessary and won't free it as long as it
// exists.  If you call GetBlock() then it should be followed by a FreeBlock()
//when you are done with it.
type Manager struct {
  mutex  sync.Mutex

  // blocks[size] = a slice of blocks of length 2^(10+size)
  blocks [][][]byte

  // Map of used blocks.  The key is the address of the first byte in the
  // block.
  used   map[*byte]bool
}

func NewManager() *Manager {
  var m Manager
  m.blocks = make([][][]byte, 21)
  m.used = make(map[*byte]bool)
  return &m
}

// Returns a slice of length n of the smallest block available that is at
// least that large.  Will reuse an existing block if possible, otherwise
// it will allocate a new one.
func (m *Manager) GetBlock(n int) []byte {
  m.mutex.Lock()
  defer m.mutex.Unlock()

  // Find the smallest block that can accomodate the request.
  c := smallestBlockSize
  s := 0
  for c < n {
    c *= 2
    s++
  }

  // Check if a block already exists of the appropriate size.
  for i := range m.blocks[s] {
    if !m.used[&m.blocks[s][i][0]] {
      m.used[&m.blocks[s][i][0]] = true
      for j := range m.blocks[s][i] {
        m.blocks[s][i][j] = 0
      }
      return m.blocks[s][i]
    }
  }

  // Otherwise allocate a new block to use.
  new_block := make([]byte, c)
  m.blocks[s] = append(m.blocks[s], new_block)
  m.used[&new_block[0]] = true
  return new_block[0:n]
}

// Returns a block back to the Manager so that it can be reused.  Panics if
// this block has already been freed, or if it was not allocated by this
// manager.
func (m *Manager) FreeBlock(b []byte) {
  m.mutex.Lock()
  defer m.mutex.Unlock()
  if _, ok := m.used[&b[0]]; !ok {
    panic("Tried to free an unused block")
  }
  delete(m.used, &b[0])
}

// Human-readable rundown on how many blocks have been reserved by this
// manager, and which ones are currently in use.
func (m *Manager) TotalAllocations() string {
  c := smallestBlockSize
  var ret string
  total_used := 0
  total_allocated := 0
  for s := range m.blocks {
    used := 0
    for i := range m.blocks[s] {
      if m.used[&m.blocks[s][i][0]] {
        used++
      }
    }
    if used > 0 {
      ret += fmt.Sprintf("%d bytes: %d/%d blocks in use.\n", c, used, len(m.blocks[s]))
    }
    total_used += used * c
    total_allocated += len(m.blocks[s]) * c
    c *= 2
  }
  ret += fmt.Sprintf("Total memory used/allocated: %d/%d\n", total_used, total_allocated)
  return ret
}

var manager *Manager
func init() {
  manager = NewManager()
}

// Returns a slice of length n of the smallest block available that is at
// least that large.  Will reuse an existing block if possible, otherwise
// it will allocate a new one.
func GetBlock(n int) []byte {
  return manager.GetBlock(n)
}

// Returns a block back to the Manager so that it can be reused.  Panics if
// this block has already been freed, or if it was not allocated by this
// manager.
func FreeBlock(b []byte) {
  manager.FreeBlock(b)
}

// Human-readable rundown on how many blocks have been reserved by this
// manager, and which ones are currently in use.
func TotalAllocations() string {
  return manager.TotalAllocations()
}<commit_msg>ran gofmt<commit_after>// In cases where Go's garbage collecter is unresponsive, or even broken, this
// allows you to easily manage blocks of memory.  This can be useful if you
// anticipate churning through lots of memory very quickly.  This package
// creates a default Manager that can be used through the package functions,
// but you can create your own Manager if you want to eventually return the
// memory back to Go.
package memory

import (
  "fmt"
  "sync"
)

// We don't bother allocating blocks smaller than this.  This choice is
// somewhat arbitrary, but should probably be appropriate for most
// applications that want to manage their own memory.
const smallestBlockSize = 1024

// A Manager allocates memory as necessary and won't free it as long as it
// exists.  If you call GetBlock() then it should be followed by a FreeBlock()
//when you are done with it.
type Manager struct {
  mutex sync.Mutex

  // blocks[size] = a slice of blocks of length 2^(10+size)
  blocks [][][]byte

  // Map of used blocks.  The key is the address of the first byte in the
  // block.
  used map[*byte]bool
}

func NewManager() *Manager {
  var m Manager
  m.blocks = make([][][]byte, 21)
  m.used = make(map[*byte]bool)
  return &m
}

// Returns a slice of length n of the smallest block available that is at
// least that large.  Will reuse an existing block if possible, otherwise
// it will allocate a new one.
func (m *Manager) GetBlock(n int) []byte {
  m.mutex.Lock()
  defer m.mutex.Unlock()

  // Find the smallest block that can accomodate the request.
  c := smallestBlockSize
  s := 0
  for c < n {
    c *= 2
    s++
  }

  // Check if a block already exists of the appropriate size.
  for i := range m.blocks[s] {
    if !m.used[&m.blocks[s][i][0]] {
      m.used[&m.blocks[s][i][0]] = true
      for j := range m.blocks[s][i] {
        m.blocks[s][i][j] = 0
      }
      return m.blocks[s][i]
    }
  }

  // Otherwise allocate a new block to use.
  new_block := make([]byte, c)
  m.blocks[s] = append(m.blocks[s], new_block)
  m.used[&new_block[0]] = true
  return new_block[0:n]
}

// Returns a block back to the Manager so that it can be reused.  Panics if
// this block has already been freed, or if it was not allocated by this
// manager.
func (m *Manager) FreeBlock(b []byte) {
  m.mutex.Lock()
  defer m.mutex.Unlock()
  if _, ok := m.used[&b[0]]; !ok {
    panic("Tried to free an unused block")
  }
  delete(m.used, &b[0])
}

// Human-readable rundown on how many blocks have been reserved by this
// manager, and which ones are currently in use.
func (m *Manager) TotalAllocations() string {
  c := smallestBlockSize
  var ret string
  total_used := 0
  total_allocated := 0
  for s := range m.blocks {
    used := 0
    for i := range m.blocks[s] {
      if m.used[&m.blocks[s][i][0]] {
        used++
      }
    }
    if used > 0 {
      ret += fmt.Sprintf("%d bytes: %d/%d blocks in use.\n", c, used, len(m.blocks[s]))
    }
    total_used += used * c
    total_allocated += len(m.blocks[s]) * c
    c *= 2
  }
  ret += fmt.Sprintf("Total memory used/allocated: %d/%d\n", total_used, total_allocated)
  return ret
}

var manager *Manager

func init() {
  manager = NewManager()
}

// Returns a slice of length n of the smallest block available that is at
// least that large.  Will reuse an existing block if possible, otherwise
// it will allocate a new one.
func GetBlock(n int) []byte {
  return manager.GetBlock(n)
}

// Returns a block back to the Manager so that it can be reused.  Panics if
// this block has already been freed, or if it was not allocated by this
// manager.
func FreeBlock(b []byte) {
  manager.FreeBlock(b)
}

// Human-readable rundown on how many blocks have been reserved by this
// manager, and which ones are currently in use.
func TotalAllocations() string {
  return manager.TotalAllocations()
}
<|endoftext|>


<commit_before>package kupak

import (
	"fmt"
	"strings"
	"time"

	"github.com/ghodss/yaml"
)

// Manager manages installation and deploying pak to a kubernetes cluster
type Manager struct {
	kubectl Kubectl
}

// NewManager returns a Manager
func NewManager(kubectl Kubectl) (*Manager, error) {
	return &Manager{kubectl: kubectl}, nil
}

// Installed returns all installed paks in given namespace
func (m *Manager) Installed(namespace string) ([]*InstalledPak, error) {
	objects, err := m.kubectl.Get(namespace, "all", "")
	if err != nil {
		return nil, err
	}

	// group all paks
	groups := make(map[string][]*Object)
	for i := range objects {
		md, err := objects[i].Metadata()
		if err != nil {
			return nil, err
		}
		group, has := md.Labels["kp-group"]
		if has {
			groups[group] = append(groups[group], objects[i])
		}
	}

	var installedPaks []*InstalledPak
	for k, v := range groups {
		// create InstalledPak objects from group
		installedPak := &InstalledPak{}
		installedPak.Statuses = make(map[string]*PodStatus)
		installedPak.Group = k
		installedPak.Objects = v
		for i := range v {
			md, err := v[i].Metadata()
			if err != nil {
				return nil, err
			}

			// find url
			if url, has := md.Annotations["kp-pak-url"]; installedPak.PakURL == "" && has {
				installedPak.PakURL = url
			}

			// find namespace
			if installedPak.Namespace == "" {
				installedPak.Namespace = md.Namespace
			}

			// extracting all pod statuses
			if strings.ToLower(md.Kind) == "pod" {
				status, err := v[i].Status()
				if err != nil {
					return nil, err
				}
				installedPak.Statuses[md.Name] = status
			}

			// properties values
			if propertiesRaw, has := md.Annotations["kp-pak-properties"]; installedPak.PropertiesValues == nil && has {
				var properties map[string]interface{}
				if err := yaml.Unmarshal([]byte(propertiesRaw), &properties); err != nil {
					return nil, err
				}
				installedPak.PropertiesValues = properties
			}
		}
		installedPaks = append(installedPaks, installedPak)
	}
	return installedPaks, nil
}

// TODO
// func (m *Manager) Instances(namespace string, pak *Pak) ([]*InstalledPak, error)
// func (m *Manager) Status(namespace string, instance string) (*InstalledPak, error)

// Install a pak with given name and returns its group
func (m *Manager) Install(pak *Pak, namespace string, properties map[string]interface{}) (string, error) {
	var group string
	// try to get group from properties, if it doesn't exists create a random
	// group and add it to properties
	if val, has := properties["group"]; has {
		group = val.(string)
	} else {
		group = generateRandomGroup()
		properties["group"] = group
	}

	// execute the templates
	rawObjects, err := pak.ExecuteTemplates(properties)
	if err != nil {
		return "", err
	}

	// labels and annotations
	labels := map[string]string{
		"kp-group":  group,
		"kp-pak-id": pak.ID(),
	}
	propertiesYaml, err := yaml.Marshal(properties)
	if err != nil {
		return "", err
	}
	annotations := map[string]string{
		"kp-pak-url":        pak.URL,
		"kp-created-time":   time.Now().String(),
		"kp-pak-properties": string(propertiesYaml),
	}
	var objects []*Object
	for i := range rawObjects {
		object, err := NewObject(rawObjects[i])
		if err != nil {
			return "", err
		}

		md, err := object.Metadata()
		if err != nil {
			return "", err
		}

		mergedLabels := mergeStringMaps(md.Labels, labels)
		if err = object.SetLabels(mergedLabels); err != nil {
			return "", err
		}
		if err = object.SetAnnotations(annotations); err != nil {
			return "", err
		}

		// TODO validation for replication controller - do not ignore
		if templateMd, err := object.TemplateMetadata(); err == nil {
			mergedLabels := mergeStringMaps(templateMd.Labels, labels)
			if err = object.SetTemplateLabels(mergedLabels); err != nil {
				return "", err
			}
		}
		objects = append(objects, object)
	}

	// install the objects
	for i := range objects {
		data, _ := objects[i].Bytes()
		fmt.Println(string(data))
		err := m.kubectl.Create(namespace, objects[i])
		if err != nil {
			// TODO XXXXXXXX rollback
			return group, fmt.Errorf("failed calling kubectl.Create: %v", err)
		}
		fmt.Println("-----")
	}

	return group, nil
}

// HasGroup checks is the specfied group is unique or not
func (m *Manager) HasGroup(group string) bool {
	return false
}

// DeleteInstance will delete a installed pak
func (m *Manager) DeleteInstance(namespace string, group string) ([]*InstalledPak, error) {
	return nil, nil
}
<commit_msg>check for group duplication<commit_after>package kupak

import (
	"fmt"
	"strings"
	"time"

	"github.com/ghodss/yaml"
)

// Manager manages installation and deploying pak to a kubernetes cluster
type Manager struct {
	kubectl Kubectl
}

// NewManager returns a Manager
func NewManager(kubectl Kubectl) (*Manager, error) {
	return &Manager{kubectl: kubectl}, nil
}

// Installed returns all installed paks in given namespace
func (m *Manager) Installed(namespace string) ([]*InstalledPak, error) {
	objects, err := m.kubectl.Get(namespace, "all", "")
	if err != nil {
		return nil, err
	}

	// group all paks
	groups := make(map[string][]*Object)
	for i := range objects {
		md, err := objects[i].Metadata()
		if err != nil {
			return nil, err
		}
		group, has := md.Labels["kp-group"]
		if has {
			groups[group] = append(groups[group], objects[i])
		}
	}

	var installedPaks []*InstalledPak
	for k, v := range groups {
		// create InstalledPak objects from group
		installedPak := &InstalledPak{}
		installedPak.Statuses = make(map[string]*PodStatus)
		installedPak.Group = k
		installedPak.Objects = v
		for i := range v {
			md, err := v[i].Metadata()
			if err != nil {
				return nil, err
			}

			// find url
			if url, has := md.Annotations["kp-pak-url"]; installedPak.PakURL == "" && has {
				installedPak.PakURL = url
			}

			// find namespace
			if installedPak.Namespace == "" {
				installedPak.Namespace = md.Namespace
			}

			// extracting all pod statuses
			if strings.ToLower(md.Kind) == "pod" {
				status, err := v[i].Status()
				if err != nil {
					return nil, err
				}
				installedPak.Statuses[md.Name] = status
			}

			// properties values
			if propertiesRaw, has := md.Annotations["kp-pak-properties"]; installedPak.PropertiesValues == nil && has {
				var properties map[string]interface{}
				if err := yaml.Unmarshal([]byte(propertiesRaw), &properties); err != nil {
					return nil, err
				}
				installedPak.PropertiesValues = properties
			}
		}
		installedPaks = append(installedPaks, installedPak)
	}
	return installedPaks, nil
}

// TODO
// func (m *Manager) Instances(namespace string, pak *Pak) ([]*InstalledPak, error)
// func (m *Manager) Status(namespace string, instance string) (*InstalledPak, error)

// Install a pak with given name and returns its group
func (m *Manager) Install(pak *Pak, namespace string, properties map[string]interface{}) (string, error) {
	var group string
	// try to get group from properties, if it doesn't exists create a random
	// group and add it to properties
	if val, has := properties["group"]; has {
		group = val.(string)
	} else {
		group = generateRandomGroup()
		properties["group"] = group
	}

	// check for group duplication
	hasGroup, err := m.HasGroup(namespace, group)
	if err != nil {
		return "", err
	}
	if hasGroup {
		return "", fmt.Errorf("Install: duplicated group '%s'", group)
	}

	// execute the templates
	rawObjects, err := pak.ExecuteTemplates(properties)
	if err != nil {
		return "", err
	}

	// apply labels and annotations
	labels := map[string]string{
		"kp-group":  group,
		"kp-pak-id": pak.ID(),
	}
	propertiesYaml, err := yaml.Marshal(properties)
	if err != nil {
		return "", err
	}
	annotations := map[string]string{
		"kp-pak-url":        pak.URL,
		"kp-created-time":   time.Now().String(),
		"kp-pak-properties": string(propertiesYaml),
	}
	var objects []*Object
	for i := range rawObjects {
		object, err := NewObject(rawObjects[i])
		if err != nil {
			return "", err
		}

		md, err := object.Metadata()
		if err != nil {
			return "", err
		}

		mergedLabels := mergeStringMaps(md.Labels, labels)
		if err = object.SetLabels(mergedLabels); err != nil {
			return "", err
		}
		if err = object.SetAnnotations(annotations); err != nil {
			return "", err
		}

		// TODO validation for replication controller - do not ignore
		if templateMd, err := object.TemplateMetadata(); err == nil {
			mergedLabels := mergeStringMaps(templateMd.Labels, labels)
			if err = object.SetTemplateLabels(mergedLabels); err != nil {
				return "", err
			}
		}
		objects = append(objects, object)
	}

	// install the objects
	for i := range objects {
		data, _ := objects[i].Bytes()
		fmt.Println(string(data))
		err := m.kubectl.Create(namespace, objects[i])
		if err != nil {
			// TODO XXXXXXXX rollback
			return group, fmt.Errorf("failed calling kubectl.Create: %v", err)
		}
		fmt.Println("-----")
	}

	return group, nil
}

// HasGroup checks is the specfied group is unique or not
func (m *Manager) HasGroup(namespace string, group string) (bool, error) {
	objects, err := m.kubectl.Get(namespace, "all", "kp-group="+group)
	if err != nil {
		return true, fmt.Errorf("HasGroup: %v", err)
	}
	if len(objects) > 0 {
		return true, nil
	}
	return false, nil
}

// DeleteInstance will delete a installed pak
func (m *Manager) DeleteInstance(namespace string, group string) ([]*InstalledPak, error) {
	return nil, nil
}
<|endoftext|>


<commit_before>package collaboration

import (
	"encoding/json"
	"os"
	"os/user"
	"path/filepath"
	"time"

	"github.com/koding/klient/Godeps/_workspace/src/github.com/boltdb/bolt"
)

const (
	UserBucket = "users"
	// Canonical database path is `$HOME + DatabasePath`
	DatabasePath = "/.config/koding/klient.bolt"
)

// boltdb satisfies Storage interface
type boltdb struct {
	*bolt.DB
}

// Tx is our own transaction type which provides helper methods
type Tx struct {
	*bolt.Tx
}

// NewBoltStorage returns a new boltdb
func NewBoltStorage() (*boltdb, error) {
	d := &boltdb{}

	// Ensure data directory exists.
	u, err := user.Current()
	if err != nil {
		return nil, err
	}
	boltPath := u.HomeDir + DatabasePath
	if err := os.MkdirAll(filepath.Dir(boltPath), 0755); err != nil {
		return nil, err
	}

	if err := d.open(boltPath); err != nil {
		return nil, err
	}

	return d, nil
}

func (b *boltdb) open(dbpath string) error {
	var err error
	b.DB, err = bolt.Open(dbpath, 0644, &bolt.Options{Timeout: 5 * time.Second})
	if err != nil {
		return err
	}

	return b.Update(func(tx *Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte(UserBucket))
		if err != nil {
			return err
		}
		return nil
	})
}

// Get returns the value of a given username
func (b *boltdb) Get(username string) (*Option, error) {
	var option *Option
	err := b.View(func(tx *Tx) error {
		value := tx.User(username)
		if len(value) == 0 {
			return ErrUserNotFound
		}

		return json.Unmarshal([]byte(value), &option)
	})

	return option, err
}

// GetAll fetches all keys which are unique usernames in the bucket.
func (b *boltdb) GetAll() (map[string]*Option, error) {
	options := make(map[string]*Option, 0)

	err := b.View(func(tx *Tx) error {
		return tx.Bucket([]byte(UserBucket)).ForEach(func(k, v []byte) error {
			var option *Option
			if err := json.Unmarshal(v, &option); err != nil {
				return err
			}

			options[string(k)] = option
			return nil
		})
	})

	return options, err
}

// Set assigns the value for the given username
func (b *boltdb) Set(username string, option *Option) error {
	v, err := json.Marshal(option)
	if err != nil {
		return err
	}

	return b.Update(func(tx *Tx) error {
		return tx.SetUser(username, string(v))
	})
}

// Delete deletes the given username key and value from the bucket
func (b *boltdb) Delete(username string) error {
	return b.Update(func(tx *Tx) error {
		return tx.DeleteUser(username)
	})
}

// Close closes the boltdb database for further read and writes
func (b *boltdb) Close() error {
	return b.DB.Close()
}

// View executes a function in the context of a read-only transaction.
func (b *boltdb) View(fn func(*Tx) error) error {
	return b.DB.View(func(tx *bolt.Tx) error {
		return fn(&Tx{tx})
	})
}

// Update executes a function in the context of a writable transaction.
func (b *boltdb) Update(fn func(*Tx) error) error {
	return b.DB.Update(func(tx *bolt.Tx) error {
		return fn(&Tx{tx})
	})
}

// User retrieves a users field by name.
func (tx *Tx) User(key string) string {
	return string(tx.Bucket([]byte(UserBucket)).Get([]byte(key)))
}

// SetUser sets the value of a users field by name.
func (tx *Tx) SetUser(key, value string) error {
	return tx.Bucket([]byte(UserBucket)).Put([]byte(key), []byte(value))
}

// DeleteUser deletes the key of a users field by name.
func (tx *Tx) DeleteUser(key string) error {
	return tx.Bucket([]byte(UserBucket)).Delete([]byte(key))
}
<commit_msg>collaboration: don't return an error, good for lazy migration<commit_after>package collaboration

import (
	"encoding/json"
	"os"
	"os/user"
	"path/filepath"
	"time"

	"github.com/koding/klient/Godeps/_workspace/src/github.com/boltdb/bolt"
)

const (
	UserBucket = "users"
	// Canonical database path is `$HOME + DatabasePath`
	DatabasePath = "/.config/koding/klient.bolt"
)

// boltdb satisfies Storage interface
type boltdb struct {
	*bolt.DB
}

// Tx is our own transaction type which provides helper methods
type Tx struct {
	*bolt.Tx
}

// NewBoltStorage returns a new boltdb
func NewBoltStorage() (*boltdb, error) {
	d := &boltdb{}

	// Ensure data directory exists.
	u, err := user.Current()
	if err != nil {
		return nil, err
	}
	boltPath := u.HomeDir + DatabasePath
	if err := os.MkdirAll(filepath.Dir(boltPath), 0755); err != nil {
		return nil, err
	}

	if err := d.open(boltPath); err != nil {
		return nil, err
	}

	return d, nil
}

func (b *boltdb) open(dbpath string) error {
	var err error
	b.DB, err = bolt.Open(dbpath, 0644, &bolt.Options{Timeout: 5 * time.Second})
	if err != nil {
		return err
	}

	return b.Update(func(tx *Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte(UserBucket))
		if err != nil {
			return err
		}
		return nil
	})
}

// Get returns the value of a given username
func (b *boltdb) Get(username string) (*Option, error) {
	var option *Option
	err := b.View(func(tx *Tx) error {
		value := tx.User(username)
		if len(value) == 0 {
			return ErrUserNotFound
		}

		// don't unmarshall an empty string, this is probably due the old db
		if string(value) == "" {
			return nil
		}

		return json.Unmarshal([]byte(value), &option)
	})

	return option, err
}

// GetAll fetches all keys which are unique usernames in the bucket.
func (b *boltdb) GetAll() (map[string]*Option, error) {
	options := make(map[string]*Option, 0)

	err := b.View(func(tx *Tx) error {
		return tx.Bucket([]byte(UserBucket)).ForEach(func(k, v []byte) error {
			var option *Option

			// we don't return an error, because of the old data which is pure
			// string
			if err := json.Unmarshal(v, &option); err == nil {
				options[string(k)] = option
			}

			return nil
		})
	})

	return options, err
}

// Set assigns the value for the given username
func (b *boltdb) Set(username string, option *Option) error {
	v, err := json.Marshal(option)
	if err != nil {
		return err
	}

	return b.Update(func(tx *Tx) error {
		return tx.SetUser(username, string(v))
	})
}

// Delete deletes the given username key and value from the bucket
func (b *boltdb) Delete(username string) error {
	return b.Update(func(tx *Tx) error {
		return tx.DeleteUser(username)
	})
}

// Close closes the boltdb database for further read and writes
func (b *boltdb) Close() error {
	return b.DB.Close()
}

// View executes a function in the context of a read-only transaction.
func (b *boltdb) View(fn func(*Tx) error) error {
	return b.DB.View(func(tx *bolt.Tx) error {
		return fn(&Tx{tx})
	})
}

// Update executes a function in the context of a writable transaction.
func (b *boltdb) Update(fn func(*Tx) error) error {
	return b.DB.Update(func(tx *bolt.Tx) error {
		return fn(&Tx{tx})
	})
}

// User retrieves a users field by name.
func (tx *Tx) User(key string) string {
	return string(tx.Bucket([]byte(UserBucket)).Get([]byte(key)))
}

// SetUser sets the value of a users field by name.
func (tx *Tx) SetUser(key, value string) error {
	return tx.Bucket([]byte(UserBucket)).Put([]byte(key), []byte(value))
}

// DeleteUser deletes the key of a users field by name.
func (tx *Tx) DeleteUser(key string) error {
	return tx.Bucket([]byte(UserBucket)).Delete([]byte(key))
}
<|endoftext|>


<commit_before>/**
 *  Copyright 2014 Paul Querna
 *
 *  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 ffjsoninception

import (
	"reflect"
)

func typeInInception(ic *Inception, typ reflect.Type) bool {
	for _, v := range ic.objs {
		if v.Typ == typ {
			return true
		}
	}

	return false
}

func getOmitEmpty(ic *Inception, sf *StructField) string {
	switch sf.Typ.Kind() {

	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
		return "if len(mj." + sf.Name + ") != 0 {" + "\n"

	case reflect.Int,
		reflect.Int8,
		reflect.Int16,
		reflect.Int32,
		reflect.Int64,
		reflect.Uint,
		reflect.Uint8,
		reflect.Uint16,
		reflect.Uint32,
		reflect.Uint64,
		reflect.Uintptr,
		reflect.Float32,
		reflect.Float64:
		return "if mj." + sf.Name + " != 0 {" + "\n"

	case reflect.Bool:
		return "if mj." + sf.Name + " != false {" + "\n"

	case reflect.Interface, reflect.Ptr:
		// TODO(pquerna): pointers. oops.
		return "if mj." + sf.Name + " != nil {" + "\n"

	default:
		// TODO(pquerna): fix types
		return "if true {" + "\n"
	}
}

func getGetInnerValue(ic *Inception, name string, typ reflect.Type) string {
	var out = ""
	if typ.Implements(marshalerType) || typeInInception(ic, typ) {
		out += "obj, err = " + name + ".MarshalJSON()" + "\n"
		out += "if err != nil {" + "\n"
		out += "  return nil, err" + "\n"
		out += "}" + "\n"
		out += "buf.Write(obj)" + "\n"
		return out
	}

	switch typ.Kind() {
	case reflect.Int,
		reflect.Int8,
		reflect.Int16,
		reflect.Int32,
		reflect.Int64:
		ic.OutputImports[`"strconv"`] = true
		out += "buf.Write(strconv.AppendInt([]byte{}, int64(" + name + "), 10))" + "\n"
	case reflect.Uint,
		reflect.Uint8,
		reflect.Uint16,
		reflect.Uint32,
		reflect.Uint64,
		reflect.Uintptr:
		ic.OutputImports[`"strconv"`] = true
		out += "buf.Write(strconv.AppendUint([]byte{}, uint64(" + name + "), 10))" + "\n"
	case reflect.Float32,
		reflect.Float64:
		ic.OutputImports[`"strconv"`] = true
		out += "buf.Write(strconv.AppendFloat([]byte{}, float64(" + name + "), 10))" + "\n"
	case reflect.Array:
		out += "buf.WriteString(`[`)" + "\n"
		out += "for _, v := range " + name + "{" + "\n"
		out += getGetInnerValue(ic, "v", typ.Elem())
		out += "}"
		out += "buf.WriteString(`]`)" + "\n"
	case reflect.String:
		out += "buf.WriteString(`\"`)" + "\n"
		out += "buf.WriteString(" + name + ")" + "\n"
		out += "buf.WriteString(`\"`)" + "\n"
	default:
		// println(sf.Typ)
		ic.OutputImports[`"encoding/json"`] = true
		out += "obj, err = json.Marshal(" + name + ")" + "\n"
		out += "if err != nil {" + "\n"
		out += "  return nil, err" + "\n"
		out += "}" + "\n"
		out += "buf.Write(obj)" + "\n"
	}
	return out
}

func getValue(ic *Inception, sf *StructField) string {
	return getGetInnerValue(ic, "mj."+sf.Name, sf.Typ)
}

func CreateMarshalJSON(ic *Inception, si *StructInfo) error {
	var out = ""

	ic.OutputImports[`"bytes"`] = true

	out += `func (mj *` + si.Name + `) MarshalJSON() ([]byte, error) {` + "\n"
	out += `var buf bytes.Buffer` + "\n"
	out += `var err error` + "\n"
	out += `var obj []byte` + "\n"
	out += `var first bool = true` + "\n"
	out += `_ = obj` + "\n"
	out += `_ = err` + "\n"
	out += `_ = first` + "\n"
	out += "buf.WriteString(`{`)" + "\n"

	for _, f := range si.Fields {
		if f.JsonName == "-" {
			continue
		}

		if f.OmitEmpty {
			out += getOmitEmpty(ic, f)
		}

		out += "if first == true {" + "\n"
		out += "first = false" + "\n"
		out += "buf.WriteString(`\"`)" + "\n"
		out += "} else {" + "\n"
		out += "buf.WriteString(`,\"`)" + "\n"
		out += "}" + "\n"

		out += "buf.WriteString(`" + f.JsonName + "`)" + "\n"
		out += "buf.WriteString(`\":`)" + "\n"
		out += getValue(ic, f)
		if f.OmitEmpty {
			out += "}" + "\n"
		}
	}

	out += "buf.WriteString(`}`)" + "\n"
	// out += "println(string(buf.Bytes()))" + "\n"
	out += `return buf.Bytes(), nil` + "\n"
	out += `}` + "\n"
	ic.OutputFuncs = append(ic.OutputFuncs, out)
	return nil
}
<commit_msg>Hack around with pre-growing the bytes.Buffer.<commit_after>/**
 *  Copyright 2014 Paul Querna
 *
 *  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 ffjsoninception

import (
	"reflect"
)

func typeInInception(ic *Inception, typ reflect.Type) bool {
	for _, v := range ic.objs {
		if v.Typ == typ {
			return true
		}
	}

	return false
}

func getOmitEmpty(ic *Inception, sf *StructField) string {
	switch sf.Typ.Kind() {

	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
		return "if len(mj." + sf.Name + ") != 0 {" + "\n"

	case reflect.Int,
		reflect.Int8,
		reflect.Int16,
		reflect.Int32,
		reflect.Int64,
		reflect.Uint,
		reflect.Uint8,
		reflect.Uint16,
		reflect.Uint32,
		reflect.Uint64,
		reflect.Uintptr,
		reflect.Float32,
		reflect.Float64:
		return "if mj." + sf.Name + " != 0 {" + "\n"

	case reflect.Bool:
		return "if mj." + sf.Name + " != false {" + "\n"

	case reflect.Interface, reflect.Ptr:
		// TODO(pquerna): pointers. oops.
		return "if mj." + sf.Name + " != nil {" + "\n"

	default:
		// TODO(pquerna): fix types
		return "if true {" + "\n"
	}
}

func getGetInnerValue(ic *Inception, name string, typ reflect.Type) string {
	var out = ""
	if typ.Implements(marshalerType) || typeInInception(ic, typ) {
		out += "obj, err = " + name + ".MarshalJSON()" + "\n"
		out += "if err != nil {" + "\n"
		out += "  return nil, err" + "\n"
		out += "}" + "\n"
		out += "buf.Write(obj)" + "\n"
		return out
	}

	switch typ.Kind() {
	case reflect.Int,
		reflect.Int8,
		reflect.Int16,
		reflect.Int32,
		reflect.Int64:
		ic.OutputImports[`"strconv"`] = true
		out += "buf.Write(strconv.AppendInt([]byte{}, int64(" + name + "), 10))" + "\n"
	case reflect.Uint,
		reflect.Uint8,
		reflect.Uint16,
		reflect.Uint32,
		reflect.Uint64,
		reflect.Uintptr:
		ic.OutputImports[`"strconv"`] = true
		out += "buf.Write(strconv.AppendUint([]byte{}, uint64(" + name + "), 10))" + "\n"
	case reflect.Float32,
		reflect.Float64:
		ic.OutputImports[`"strconv"`] = true
		out += "buf.Write(strconv.AppendFloat([]byte{}, float64(" + name + "), 10))" + "\n"
	case reflect.Array:
		out += "buf.WriteString(`[`)" + "\n"
		out += "for _, v := range " + name + "{" + "\n"
		out += getGetInnerValue(ic, "v", typ.Elem())
		out += "}"
		out += "buf.WriteString(`]`)" + "\n"
	case reflect.String:
		out += "buf.WriteString(`\"`)" + "\n"
		out += "buf.WriteString(" + name + ")" + "\n"
		out += "buf.WriteString(`\"`)" + "\n"
	default:
		// println(sf.Typ)
		ic.OutputImports[`"encoding/json"`] = true
		out += "obj, err = json.Marshal(" + name + ")" + "\n"
		out += "if err != nil {" + "\n"
		out += "  return nil, err" + "\n"
		out += "}" + "\n"
		out += "buf.Write(obj)" + "\n"
	}
	return out
}

func getValue(ic *Inception, sf *StructField) string {
	return getGetInnerValue(ic, "mj."+sf.Name, sf.Typ)
}

func CreateMarshalJSON(ic *Inception, si *StructInfo) error {
	var out = ""

	ic.OutputImports[`"bytes"`] = true

	out += `func (mj *` + si.Name + `) MarshalJSON() ([]byte, error) {` + "\n"
	out += `var buf bytes.Buffer` + "\n"
	out += `var err error` + "\n"
	out += `var obj []byte` + "\n"
	out += `var first bool = true` + "\n"
	out += `_ = obj` + "\n"
	out += `_ = err` + "\n"
	out += `_ = first` + "\n"
	out += "buf.Grow(1024)" + "\n" // TOOD(pquerna): automatically calc a good size!
	out += "buf.WriteString(`{`)" + "\n"

	for _, f := range si.Fields {
		if f.JsonName == "-" {
			continue
		}

		if f.OmitEmpty {
			out += getOmitEmpty(ic, f)
		}

		out += "if first == true {" + "\n"
		out += "first = false" + "\n"
		out += "buf.WriteString(`\"`)" + "\n"
		out += "} else {" + "\n"
		out += "buf.WriteString(`,\"`)" + "\n"
		out += "}" + "\n"

		out += "buf.WriteString(`" + f.JsonName + "`)" + "\n"
		out += "buf.WriteString(`\":`)" + "\n"
		out += getValue(ic, f)
		if f.OmitEmpty {
			out += "}" + "\n"
		}
	}

	out += "buf.WriteString(`}`)" + "\n"
	// out += "println(string(buf.Bytes()))" + "\n"
	out += `return buf.Bytes(), nil` + "\n"
	out += `}` + "\n"
	ic.OutputFuncs = append(ic.OutputFuncs, out)
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"log"
	"os"
)

type RGB struct {
	r uint8
	g uint8
	b uint8
}

type RGBA struct {
	r uint8
	g uint8
	b uint8
	_ uint8
}

type BitmapHeader struct {
	HeaderField uint16
	Size uint32
	_ uint32
	DataAddress uint32
	DIBSize uint32
	Width uint32
	Height uint32
	ColPlanes uint16
	Bpp uint16
	_ [24]byte
}

type Header struct {
	License     [100]byte
	Name        [12]byte
	Version     [8]byte
	Timestamp   [42]byte
	FileSize    uint32
	DirectoryCount uint16
	FileCount   uint16
	Val1       [2]byte //unidentified
	Val2       [2]byte //unidentified
	Val3       [2]byte //unidentified
	Val4       [2]byte //unidentified
	Val5       [2]byte //unidentified
}

type DirectoryInfo struct {
	Name  [4]byte
	Count uint16
	Pos   uint16
}

type FileInfo struct {
	Name [12]byte
	ID uint16 // 0 = Default, 200 = BMP, 1000 = TXT
	Size uint32
	Addr uint32
	Val1 uint8 //unidentified
	Val2 uint8 //unidentified
	Val3 uint8 //unidentified
	Val4 uint8 //unidentified
}

func check(e error) {
	if e != nil {
		log.Fatal(e)
	}
}

func insertByte(slice []byte, index int, value byte) []byte {
	s_a := slice[:index+1]
	s_b := slice[index:]
	s_a = append(s_a, value)
	s_a = append(s_a, s_b...)
	return s_a
}

func readNumBytes(file *os.File, number int) []byte {
	bytes := make([]byte, number)
	num, err := file.Read(bytes)
	if num != number {
		fmt.Printf("Ran out of bytes! (wanted: %d, got: %d)\n", number, num)
	}
	check(err)
	return bytes
}

func getBuffer(f *os.File, n int) *bytes.Buffer {
	data := readNumBytes(f, n)
	buffer := bytes.NewBuffer(data)
	return buffer
}

func unpackHeader(f *os.File, hdrSize int) *Header {
	hdr := Header{}
	err := binary.Read(getBuffer(f, hdrSize), binary.LittleEndian, &hdr)
	check(err)
	return &hdr
}

func unpackDirectoryList(f *os.File, cnt int) []*DirectoryInfo {
	dir_list := make([]*DirectoryInfo, cnt)
	for i := 0; i < cnt; i++ {
		dir := DirectoryInfo{}
		err := binary.Read(getBuffer(f, 8), binary.LittleEndian, &dir)
		check(err)
		dir_list[i] = &dir
	}
	return dir_list
}

func unpackFileList(f *os.File, cnt int) []*FileInfo {
	file_list := make([]*FileInfo, cnt)
	for i := 0; i < cnt; i++ {
		file := FileInfo{}
		err := binary.Read(getBuffer(f, 26), binary.LittleEndian, &file)
		check(err)
		file_list[i] = &file
	}
	return file_list
}

func unpackFile(f *os.File, file *FileInfo) []byte {
	addr := int64(file.Addr)
	fsize := int(file.Size)
	f.Seek(addr, 0)
	file_data := readNumBytes(f, fsize)

	return file_data
}

func getPalette(f *os.File, dir_list []*DirectoryInfo, files []*FileInfo, s string) []*RGB {
	for _, dir := range dir_list {
		if string(dir.Name[:3]) == "PAL" {
			fmt.Printf("PAL directory found\n")
			for _, file := range files[dir.Pos:dir.Pos + dir.Count] {
				file_name := string(bytes.Trim(file.Name[:12], "x\000"))
				if file_name == s {
					fmt.Printf("Unpacking palette: %s\n", file_name)
					palette := make([]*RGB, 256)
					f.Seek(int64(file.Addr), 0)
					for i := 0; i < 256; i++ {
						pal := readNumBytes(f, 3)
						pal_entry := RGB{
							r : pal[0],
							g : pal[1],
							b : pal[2],
						}
						fmt.Printf("%+x\n",pal_entry)
						palette[i] = &pal_entry
					}
					return palette
				}
			}
		}
	}
	log.Fatal("Couldn't find requested PAL file")
	return nil
}

func unpackFiles(f *os.File, hdr *Header, dir_list []*DirectoryInfo, files []*FileInfo, pal []*RGB)  {
	var buf bytes.Buffer

	for _, dir := range dir_list {
		work_dir := "./" +  string(bytes.Trim(hdr.Name[:8], "x\000")) + "/" +
			string(dir.Name[:3]) + "/"
		fmt.Printf("Extracting to %s\n", work_dir)
		os.MkdirAll(work_dir, os.ModePerm)
		fmt.Printf("File count: %d\n", dir.Count)

		for _, file := range files[dir.Pos:dir.Count + dir.Pos] {
			s := work_dir + string(bytes.Trim(file.Name[:12], "x\000"))
			out, err := os.Create(s)
			check(err)

			out_data := unpackFile(f, file)

			//fmt.Printf("Filename: %s\n ID: %x\n Val1: %x\n Val2: %x\n Val3: %x\n Val4: %x\n",
			//	file.Name, file.ID, file.Val1, file.Val2, file.Val3, file.Val4)
			switch file.ID {
			case 0x200: //Bitmap
				dim := out_data[:4]
				bmp_x := uint32(binary.LittleEndian.Uint16(dim[:2]))
				bmp_y := uint32(binary.LittleEndian.Uint16(dim[2:]))
				bmp_data := out_data[4:]
				bmp_header := BitmapHeader{
					HeaderField: 0x4d42,
					Size: uint32(0x43B + file.Size),
					DataAddress: 0x43B,
					DIBSize: 0x28,
					Width: bmp_x,
					Height: bmp_y,
					ColPlanes: 0x1,
					Bpp: 0x8,
				}

				//Some bitmaps are not 4-byte aligned, so we need to pad them manually
				row := int(bmp_x)
				if row % 4 != 0 {
					fmt.Printf("File %s requires padding\n", file.Name)
					for i := row; i < len(bmp_data) - row; i += row {
						fmt.Printf("Row: %d\n", row)
						for ii := 1; ii < row % 4; ii++ {
							bmp_data = insertByte(bmp_data, i, 0)
						}
					}
				}

				binary.Write(&buf, binary.LittleEndian, bmp_header)

				//PAL values are 0x00 - 0x3F, and red/blue channels seem to be swapped
				for i := 0; i < len(pal); i++ {
					outpal_entry := RGBA{
						r : pal[i].b * 4,
						g : pal[i].g * 4,
						b : pal[i].r * 4,
					}
					binary.Write(&buf, binary.LittleEndian, outpal_entry)
				}

				binary.Write(&buf, binary.LittleEndian, bmp_data)

				bmp_file := make([]byte, buf.Len())
				err = binary.Read(&buf, binary.LittleEndian, bmp_file)
				check(err)
				_, err = out.Write(bmp_file)
				check(err)

			default:
				_, err = out.Write(out_data)
				check(err)
			}
			out.Close()
		}
	}
}

func main() {
	var hdrSize int

	if len(os.Args) == 1 {
		fmt.Printf("Usage: rsfunpack FILE\n")
		return
	}
	path := os.Args[1]

	f, err := os.Open(path)
	check(err)
	defer f.Close()

	fmt.Printf("%s opened\n", path)

	formatCheck := readNumBytes(f, 1)

	if formatCheck[0] == byte(0x41) {
		fmt.Printf("Valid RSF format found\n")
		hdrSize = 0xb4
	} else if formatCheck[0] == byte(0x6c) {
		log.Fatal("Cannot handle old-style RSF format\n")
	} else {
		log.Fatal("Unknown file format\n")
	}

	f.Seek(0, 0)
	header := unpackHeader(f, hdrSize)

	fmt.Printf("%s\n%s\n%s\n%s\nFilesize: %d\nFormats: %d Files: %d\n", header.License, header.Name,
		header.Version, header.Timestamp, header.FileSize, header.DirectoryCount, header.FileCount)

	directory_list := unpackDirectoryList(f, int(header.DirectoryCount))
	file_list := unpackFileList(f, int(header.FileCount))

	rgb_pal := getPalette(f, directory_list, file_list, "TRUERGB.PAL")
	//l23_pal := getPalette(f, header, format_list, file_list, "L23.PAL")

	unpackFiles(f, header, directory_list, file_list, rgb_pal)
}
<commit_msg>Changes to BMP padding behaviour<commit_after>package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"log"
	"os"
)

type RGB struct {
	r uint8
	g uint8
	b uint8
}

type RGBA struct {
	r uint8
	g uint8
	b uint8
	_ uint8
}

type BitmapHeader struct {
	HeaderField uint16
	Size uint32
	_ uint32
	DataAddress uint32
	DIBSize uint32
	Width uint32
	Height uint32
	ColPlanes uint16
	Bpp uint16
	_ [24]byte
}

type Header struct {
	License     [100]byte
	Name        [12]byte
	Version     [8]byte
	Timestamp   [42]byte
	FileSize    uint32
	DirectoryCount uint16
	FileCount   uint16
	Val1       [2]byte //unidentified
	Val2       [2]byte //unidentified
	Val3       [2]byte //unidentified
	Val4       [2]byte //unidentified
	Val5       [2]byte //unidentified
}

type DirectoryInfo struct {
	Name  [4]byte
	Count uint16
	Pos   uint16
}

type FileInfo struct {
	Name [12]byte
	ID uint16 // 0 = Default, 200 = BMP, 1000 = TXT
	Size uint32
	Addr uint32
	Val1 uint8 //unidentified
	Val2 uint8 //unidentified
	Val3 uint8 //unidentified
	Val4 uint8 //unidentified
}

func check(e error) {
	if e != nil {
		log.Fatal(e)
	}
}

func insertByte(slice []byte, index int, value byte) []byte {
	s_a := slice[:index+1]
	s_b := slice[index:]
	s_a = append(s_a, value)
	s_a = append(s_a, s_b...)
	return s_a
}

func readNumBytes(file *os.File, number int) []byte {
	bytes := make([]byte, number)
	num, err := file.Read(bytes)
	if num != number {
		fmt.Printf("Ran out of bytes! (wanted: %d, got: %d)\n", number, num)
	}
	check(err)
	return bytes
}

func getBuffer(f *os.File, n int) *bytes.Buffer {
	data := readNumBytes(f, n)
	buffer := bytes.NewBuffer(data)
	return buffer
}

func unpackHeader(f *os.File, hdrSize int) *Header {
	hdr := Header{}
	err := binary.Read(getBuffer(f, hdrSize), binary.LittleEndian, &hdr)
	check(err)
	return &hdr
}

func unpackDirectoryList(f *os.File, cnt int) []*DirectoryInfo {
	dir_list := make([]*DirectoryInfo, cnt)
	for i := 0; i < cnt; i++ {
		dir := DirectoryInfo{}
		err := binary.Read(getBuffer(f, 8), binary.LittleEndian, &dir)
		check(err)
		dir_list[i] = &dir
	}
	return dir_list
}

func unpackFileList(f *os.File, cnt int) []*FileInfo {
	file_list := make([]*FileInfo, cnt)
	for i := 0; i < cnt; i++ {
		file := FileInfo{}
		err := binary.Read(getBuffer(f, 26), binary.LittleEndian, &file)
		check(err)
		file_list[i] = &file
	}
	return file_list
}

func unpackFile(f *os.File, file *FileInfo) []byte {
	addr := int64(file.Addr)
	fsize := int(file.Size)
	f.Seek(addr, 0)
	file_data := readNumBytes(f, fsize)

	return file_data
}

func getPalette(f *os.File, dir_list []*DirectoryInfo, files []*FileInfo, s string) []*RGB {
	for _, dir := range dir_list {
		if string(dir.Name[:3]) == "PAL" {
			fmt.Printf("PAL directory found\n")
			for _, file := range files[dir.Pos:dir.Pos + dir.Count] {
				file_name := string(bytes.Trim(file.Name[:12], "x\000"))
				if file_name == s {
					fmt.Printf("Unpacking palette: %s\n", file_name)
					palette := make([]*RGB, 256)
					f.Seek(int64(file.Addr), 0)
					for i := 0; i < 256; i++ {
						pal := readNumBytes(f, 3)
						pal_entry := RGB{
							r : pal[0],
							g : pal[1],
							b : pal[2],
						}
						fmt.Printf("%+x\n",pal_entry)
						palette[i] = &pal_entry
					}
					return palette
				}
			}
		}
	}
	log.Fatal("Couldn't find requested PAL file")
	return nil
}

func unpackFiles(f *os.File, hdr *Header, dir_list []*DirectoryInfo, files []*FileInfo, pal []*RGB)  {
	var buf bytes.Buffer

	for _, dir := range dir_list {
		work_dir := "./" +  string(bytes.Trim(hdr.Name[:8], "x\000")) + "/" +
			string(dir.Name[:3]) + "/"
		fmt.Printf("Extracting to %s\n", work_dir)
		os.MkdirAll(work_dir, os.ModePerm)
		fmt.Printf("File count: %d\n", dir.Count)

		for _, file := range files[dir.Pos:dir.Count + dir.Pos] {
			s := work_dir + string(bytes.Trim(file.Name[:12], "x\000"))
			out, err := os.Create(s)
			check(err)

			out_data := unpackFile(f, file)

			//fmt.Printf("Filename: %s\n ID: %x\n Val1: %x\n Val2: %x\n Val3: %x\n Val4: %x\n",
			//	file.Name, file.ID, file.Val1, file.Val2, file.Val3, file.Val4)
			switch file.ID {
			case 0x200: //Bitmap
				dim := out_data[:4]
				bmp_x := uint32(binary.LittleEndian.Uint16(dim[:2]))
				bmp_y := uint32(binary.LittleEndian.Uint16(dim[2:]))
				bmp_data := out_data[4:]
				bmp_header := BitmapHeader{
					HeaderField: 0x4d42,
					Size: uint32(0x43B + file.Size),
					DataAddress: 0x43B,
					DIBSize: 0x28,
					Width: bmp_x,
					Height: bmp_y,
					ColPlanes: 0x1,
					Bpp: 0x8,
				}

				//Some bitmaps are not 4-byte aligned, so we need to check and pad them manually
				row := int(bmp_x)
				rowPad := -1 * ((row % 4) - 4)
				if rowPad != 4 {
					fmt.Printf("File %s requires %d bytes padding\n", file.Name, rowPad)
					for i := row + (rowPad); i < len(bmp_data); i += row + rowPad {
						for ii := 1; ii < rowPad; ii++ {
							bmp_data = insertByte(bmp_data, i+ii, 0)
						}
					}
				}

				binary.Write(&buf, binary.LittleEndian, bmp_header)

				//PAL values are 0x00 - 0x3F, and red/blue channels seem to be swapped
				for i := 0; i < len(pal); i++ {
					outpal_entry := RGBA{
						r : pal[i].b * 4,
						g : pal[i].g * 4,
						b : pal[i].r * 4,
					}
					binary.Write(&buf, binary.LittleEndian, outpal_entry)
				}

				binary.Write(&buf, binary.LittleEndian, bmp_data)

				bmp_file := make([]byte, buf.Len())
				err = binary.Read(&buf, binary.LittleEndian, bmp_file)
				check(err)
				_, err = out.Write(bmp_file)
				check(err)

			default:
				_, err = out.Write(out_data)
				check(err)
			}
			out.Close()
		}
	}
}

func main() {
	var hdrSize int

	if len(os.Args) == 1 {
		fmt.Printf("Usage: rsfunpack FILE\n")
		return
	}
	path := os.Args[1]

	f, err := os.Open(path)
	check(err)
	defer f.Close()

	fmt.Printf("%s opened\n", path)

	formatCheck := readNumBytes(f, 1)

	if formatCheck[0] == byte(0x41) {
		fmt.Printf("Valid RSF format found\n")
		hdrSize = 0xb4
	} else if formatCheck[0] == byte(0x6c) {
		log.Fatal("Cannot handle old-style RSF format\n")
	} else {
		log.Fatal("Unknown file format\n")
	}

	f.Seek(0, 0)
	header := unpackHeader(f, hdrSize)

	fmt.Printf("%s\n%s\n%s\n%s\nFilesize: %d\nFormats: %d Files: %d\n", header.License, header.Name,
		header.Version, header.Timestamp, header.FileSize, header.DirectoryCount, header.FileCount)

	directory_list := unpackDirectoryList(f, int(header.DirectoryCount))
	file_list := unpackFileList(f, int(header.FileCount))

	rgb_pal := getPalette(f, directory_list, file_list, "TRUERGB.PAL")
	//l23_pal := getPalette(f, header, format_list, file_list, "L23.PAL")

	unpackFiles(f, header, directory_list, file_list, rgb_pal)
}
<|endoftext|>