Related
I'm building an application where I get muslim prayer data from multiple sources. The first being S3, the second being aladhan (a public api). I only want to get data from aladhan if it's not available in S3. If I do have to get the data from the public source then I upload it to my s3.
Here is the code:
This is my interface loop code. I've put in print statements to show that I'm running into the return statement twice, once with data in my return struct, the second time the struct is nil.
// prayeriface.go
package prayer
import (
"fmt"
)
type MonthPrayerIface interface {
GetMonthPrayer(input *LookupInput) (*PreCurrNextMonthPrayer, error)
}
type PreCurrNextMonthPrayer struct {
custData *LookupInput
CurrentMonthData *PCal
PreviousMonthData *PCal
NextMonthData *PCal
prayers []MonthPrayerIface
}
func (p *PreCurrNextMonthPrayer) GetMonthPrayers() (*PreCurrNextMonthPrayer, error) {
var err error
var monthlyData *PreCurrNextMonthPrayer
defer func() {
fmt.Printf("return monthlyData address & value = %p %v\n", monthlyData, monthlyData)
}()
for k, data := range p.prayers {
fmt.Printf("loop = %v, data= %T %v\n", k, monthlyData, monthlyData)
monthlyData, err = data.GetMonthPrayer(p.custData)
fmt.Printf("\terr= %v\n", err)
fmt.Printf("\tmonthlyData= %p %v\n", monthlyData, monthlyData)
if err == nil {
fmt.Printf("loop-return: err == nil \n")
return monthlyData, nil
}
}
if err == nil {
fmt.Printf("post-loop:\n")
fmt.Printf("\tmonthlyData= %p %v\n", monthlyData, monthlyData)
return monthlyData, nil
}
return nil, fmt.Errorf("unable to get prayer data from all sources %s", err)
}
func NewMonthPrayer(input *LookupInput, prayers ...MonthPrayerIface) (*PreCurrNextMonthPrayer, error) {
var err error
t := &PreCurrNextMonthPrayer{
custData: input,
prayers: prayers,
}
t, err = t.GetMonthPrayers()
if err != nil {
return nil, err
}
return t, nil
}
As you can see, I'm looping over an interface struct method called GetMonthPrayer
This is my s3 source
// s3.go
package prayer
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"io"
awsservices "prayer-times/src/aws"
)
// S3Store s3 storage object for prayer calendars
type S3Store struct {
data *PCal
}
// GetMonthPrayer retrieves prayer data from s3, otherwise from aladhan
func (s *S3Store) GetMonthPrayer(input *LookupInput) (*PreCurrNextMonthPrayer, error) {
mPrayer := new(PreCurrNextMonthPrayer)
fmt.Println("attempting to retrieve prayer data from s3")
s3Client := awsservices.NewS3Service()
pMonthInput := &LookupInput{
Country: input.Country,
ZipCode: input.ZipCode,
custTime: input.custTime.AddDate(0, -1, 0),
}
nMonthInput := &LookupInput{
Country: input.Country,
ZipCode: input.ZipCode,
custTime: input.custTime.AddDate(0, 1, 0),
}
// s3Pdata retrieves data from S3 and
s3pData := func(input *LookupInput) (*PCal, error) {
pCalendar := new(PCal)
data, err := s3Client.GetObject(&s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(
fmt.Sprintf(
"%s/%d/%d/%d",
input.Country,
input.ZipCode,
input.custTime.Year(),
input.custTime.Month())),
})
if err != nil {
return nil, err
}
if data == nil {
return nil, errors.New("error data from s3 is nil")
}
defer func() {
err := data.Body.Close()
if err != nil {
fmt.Printf("unable to close s3 body: %s", err)
}
}()
s3buf := bytes.NewBuffer(nil)
if _, err := io.Copy(s3buf, data.Body); err != nil {
return nil, err
}
dataBytes := s3buf.Bytes()
decoder := json.NewDecoder(bytes.NewReader(dataBytes))
err = decoder.Decode(&pCalendar)
if err != nil {
fmt.Printf("unable to decode json: %s", err)
}
return pCalendar, nil
}
aladhanData := new(AladhanStore)
getAladhanData := func(input *LookupInput) (*PreCurrNextMonthPrayer, error) {
data, err := aladhanData.GetMonthPrayer(input)
if err != nil {
return nil, err
}
return data, nil
}
// Get current data from s3, if not s3, then get all three from aladhan
cMonthS3Data, err := s3pData(input)
pMonthS3Data, err := s3pData(pMonthInput)
nMonthS3Data, err := s3pData(nMonthInput)
if err != nil {
adata, err := getAladhanData(input)
if err != nil {
fmt.Printf("err: %s", err)
return nil, err
}
return adata, nil
}
mPrayer.CurrentMonthData = cMonthS3Data
// Get previous month data from s3, if not s3, then get all three from aladhan
mPrayer.PreviousMonthData = pMonthS3Data
// Get next month data from s3, if not s3, then get all three from aladhan
mPrayer.NextMonthData = nMonthS3Data
return mPrayer, nil
}
Here is my aladhan source
// aladhan.go
package prayer
import (
"bytes"
"encoding/json"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"golang.org/x/sync/errgroup"
"io"
"log"
awsservices "prayer-times/src/aws"
"prayer-times/src/urljsonoutput"
"prayer-times/src/zipcoordinates"
)
var (
aladhanURL string = "https://api.aladhan.com/v1/calendar?"
)
// PCal contains the prayer times of the month as well as the return code
type PCal struct {
Code int `json:"code"`
Status string `json:"status"`
Data []struct {
Timings DailyPrayers
}
}
/*
AladhanData returns the total monthly prayers of given month, coordinates, and zip from aladhan.
https://api.aladhan.com/v1/calendar?latitude=51.508515&longitude=-0.1254872&method=1&month=4&year=2017
*/
func AladhanData(input *LookupInput) *PCal {
coordinates := zipcoordinates.HereCoordinates(&zipcoordinates.GeoLocationInput{
PostalCode: input.ZipCode,
CountryCode: input.Country,
})
respStruct := new(PCal)
_, err := urljsonoutput.GetURLJSON(fmt.Sprintf(
"%slatitude=%v&longitude=%v&method=%v&month=%v&year=%v",
aladhanURL,
coordinates.Items[0].Position.Latitude,
coordinates.Items[0].Position.Longitude,
input.Method,
int(input.custTime.Month()),
input.custTime.Year()), respStruct)
if err != nil {
log.Fatalf("unable to pull monthly prayer data %v", err)
}
return respStruct
}
// AladhanStore struct to interact with interface for GetMonthPrayer
type AladhanStore struct {
data *PCal
}
// GetMonthPrayer Pulls prayer data from aladhan
func (a *AladhanStore) GetMonthPrayer(input *LookupInput) (*PreCurrNextMonthPrayer, error) {
mPrayer := new(PreCurrNextMonthPrayer)
// Return prayer data from aladhan
custPMonthTime := input.custTime.AddDate(0, -1, 0)
pMonthLookupInput := new(LookupInput)
pMonthLookupInput.custTime = custPMonthTime
pMonthLookupInput.ZipCode = input.ZipCode
pMonthLookupInput.Country = input.Country
custNMonthTime := input.custTime.AddDate(0, 1, 0)
nMonthLookupInput := new(LookupInput)
nMonthLookupInput.custTime = custNMonthTime
nMonthLookupInput.ZipCode = input.ZipCode
nMonthLookupInput.Country = input.Country
prayerData := AladhanData(input)
pMonthPData := AladhanData(pMonthLookupInput)
nMonthPData := AladhanData(nMonthLookupInput)
// Save prayer data into io.Reader to save to s3
var Marshal = func(data interface{}) (io.ReadSeeker, error) {
mdata, err := json.MarshalIndent(data, "", "\t")
if err != nil {
return nil, err
}
return bytes.NewReader(mdata), nil
}
rmData, err := Marshal(prayerData)
pRmData, err := Marshal(pMonthPData)
nRmData, err := Marshal(nMonthPData)
if err != nil {
return nil, err
}
// Save prayer data into s3
g := new(errgroup.Group)
s3Upload := func(rawData *io.ReadSeeker, input *LookupInput) func() error {
return func() error {
s3Client := s3manager.NewUploaderWithClient(awsservices.NewS3Service())
_, err = s3Client.Upload(&s3manager.UploadInput{
Bucket: aws.String(bucket),
Key: aws.String(
fmt.Sprintf(
"%s/%d/%d/%d",
input.Country,
input.ZipCode,
input.custTime.Year(),
int(input.custTime.Month()))),
Body: *rawData,
})
if err != nil {
return err
}
return nil
}
}
g.Go(s3Upload(&pRmData, pMonthLookupInput))
g.Go(s3Upload(&rmData, input))
g.Go(s3Upload(&nRmData, nMonthLookupInput))
if err := g.Wait(); err == nil {
mPrayer.PreviousMonthData = pMonthPData
mPrayer.CurrentMonthData = prayerData
mPrayer.NextMonthData = nMonthPData
return mPrayer, nil
}
return nil, err
}
Here is my test file.
func TestPrayer(t *testing.T) {
p, err := NewMonthPrayer(
&input,
&S3Store{},
&AladhanStore{},
)
if err != nil {
t.Errorf("error: %s", err)
}
data, err := p.GetMonthPrayers()
if err != nil {
t.Errorf("error: %s", err)
}
t.Logf("Test address: %p", data)
t.Logf("data THIS SHOULDN'T BE NIL: %v", data)
t.Logf("ERROR: %s", err)
}
These are my results. Ignore the pass result, the data is first not nil and second nil.
=== RUN TestPrayer
loop = 0, data= *prayer.PreCurrNextMonthPrayer <nil>
attempting to retrieve prayer data from s3
err= <nil>
monthlyData= 0xc000131180 &{<nil> 0xc0002612f0 0xc00051e780 0xc00011cea0 []}
loop-return: err == nil
return monthlyData address & value = 0xc000131180 &{<nil> 0xc0002612f0 0xc00051e780 0xc00011cea0 []}
post-loop:
monthlyData= 0x0 <nil>
return monthlyData address & value = 0x0 <nil>
prayer_test.go:53: Test address: 0x0
prayer_test.go:55: data THIS SHOULDN'T BE NIL: <nil>
prayer_test.go:56: ERROR: %!s(<nil>)
--- PASS: TestPrayer (0.32s)
PASS
The duplicate was due to the GetMonthPrayer call from NewMonthPrayer, which shouldn't have been the case to begin with. It was called first but returned second, thus overwriting the existing data.
func NewMonthPrayer(input *LookupInput, prayers ...MonthPrayerIface) (*PreCurrNextMonthPrayer, error) {
var err error
t := &PreCurrNextMonthPrayer{
custData: input,
prayers: prayers,
}
t, err = t.GetMonthPrayers()
if err != nil {
return nil, err
}
return t, nil
}
I removed the NewMonthPrayer entirely as it was unnecessary, I also removed the function call in the process, thus fixing the initial problem.
// NewPrayer instantiates a prayer type object with the required input
func NewPrayer(input *LookupInput, prayers ...MonthPrayerIface) *Prayer {
return &Prayer{
custData: input,
prayers: prayers,
}
}
I'm using the gopkg.in/ldap.v2 api to query an LDAP server, but was wondering how to retrieve all the attributes of an entry once it shows up in the query result? Here is the full program I have:
/*In order to use this program, the user needs to get the package by running the following command:
go get gopkg.in/ldap.v2*/
package main
import (
"fmt"
"strings"
"gopkg.in/ldap.v2"
"os"
)
//Gives constants to be used for binding to and searching the LDAP server.
const (
ldapServer = "127.0.0.1:389"
ldapBind = "cn=admin,dc=test123,dc=com"
ldapPassword = "Password"
filterDN = "(objectclass=*)"
baseDN = "dc=test123,dc=com"
loginUsername = "admin"
loginPassword = "Password"
)
//Main function, which is executed.
func main() {
conn, err := connect()
//If there is an error connecting to server, prints this
if err != nil {
fmt.Printf("Failed to connect. %s", err)
return
}
//Close the connection at a later time.
defer conn.Close()
//Declares err to be list(conn), and checks if any errors. It prints the error(s) if there are any.
if err := list(conn); err != nil {
fmt.Printf("%v", err)
return
}
/*
//Declares err to be auth(conn), and checks if any errors. It prints the error(s) if there are any.
if err := auth(conn); err != nil {
fmt.Printf("%v", err)
return
}*/
}
//This function is used to connect to the LDAP server.
func connect() (*ldap.Conn, error) {
conn, err := ldap.Dial("tcp", ldapServer)
if err != nil {
return nil, fmt.Errorf("Failed to connect. %s", err)
}
if err := conn.Bind(ldapBind, ldapPassword); err != nil {
return nil, fmt.Errorf("Failed to bind. %s", err)
}
return conn, nil
}
//This function is used to search the LDAP server as well as output the attributes of the entries.
func list(conn *ldap.Conn) error {
//This gets the command line argument and saves it in the form "(argument=*)"
arg := ""
filter := ""
if len(os.Args) > 1{
arg = os.Args[1]
fmt.Println(arg)
filter = "(" + arg + "=*)"
} else{
fmt.Println("You need to input an argument for an attribute to search. I.E. : \"go run anonymous_query.go cn\"")
}
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
fmt.Sprintf(filter),
//To add anymore strings to the search, you need to add it here.
[]string{"dn", "o", "cn", "ou", "uidNumber", "objectClass",
"uid", "uidNumber", "gidNumber", "homeDirectory", "loginShell", "gecos",
"shadowMax", "shadowWarning", "shadowLastChange", "dc", "description", "entryCSN"},
nil,
))
if err != nil {
return fmt.Errorf("Failed to search users. %s", err)
}
//Prints all the attributes per entry
for _, entry := range result.Entries {
entry.Print()
fmt.Println()
}
return nil
}
//This function authorizes the user and binds to the LDAP server.
func auth(conn *ldap.Conn) error {
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
filter(loginUsername),
[]string{"dn"},
nil,
))
if err != nil {
return fmt.Errorf("Failed to find user. %s", err)
}
if len(result.Entries) < 1 {
return fmt.Errorf("User does not exist")
}
if len(result.Entries) > 1 {
return fmt.Errorf("")
}
if err := conn.Bind(result.Entries[0].DN, loginPassword); err != nil {
fmt.Printf("Failed to auth. %s", err)
} else {
fmt.Printf("Authenticated successfuly!")
}
return nil
}
func filter(needle string) string {
res := strings.Replace(
filterDN,
"{username}",
needle,
-1,
)
return res
}
The issue I have is in this line:
//To add anymore strings to the search, you need to add it here.
[]string{"dn", "o", "cn", "ou", "uidNumber", "objectClass",
"uid", "uidNumber", "gidNumber", "homeDirectory", "loginShell", "gecos",
"shadowMax", "shadowWarning", "shadowLastChange", "dc", "description", "entryCSN"}
I would want to retrieve all the attributes of an LDAP entry rather than having to manually type all the attributes that I want from the query result. Another reason is because I don't know what attributes an entry may have.
Any help would be greatly appreciated. Thanks!
In the LDAP search operation, if you don't specify attributes for the search it will return the entries with all their attributes, so this will do the job:
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
fmt.Sprintf(filter),
[]string{},
nil,
))
I'm trying to familiarize with Goland and i'd like to make my first bot Discord with it.
I'm making a discord bot and i'm trying to make a weather command using the wego package that's use the forecast's API, here's the package and a screenshot of what it looks like in the readme: here
Here's my code :
package main
import (
"flag"
"fmt"
"github.com/bwmarrin/discordgo"
"github.com/schachmat/ingo"
_ "github.com/schachmat/wego/backends"
_ "github.com/schachmat/wego/frontends"
"github.com/schachmat/wego/iface"
"log"
"os"
"sort"
"strconv"
"strings"
)
var (
commandPrefix string
botID string
)
func pluginLists() {
bEnds := make([]string, 0, len(iface.AllBackends))
for name := range iface.AllBackends {
bEnds = append(bEnds, name)
}
sort.Strings(bEnds)
fEnds := make([]string, 0, len(iface.AllFrontends))
for name := range iface.AllFrontends {
fEnds = append(fEnds, name)
}
sort.Strings(fEnds)
fmt.Fprintln(os.Stderr, "Available backends:", strings.Join(bEnds, ", "))
fmt.Fprintln(os.Stderr, "Available frontends:", strings.Join(fEnds, ", "))
}
func main() {
discord, err := discordgo.New("Bot My_Api_Key")
errCheck("error creating discord session", err)
user, err := discord.User("#me")
errCheck("error retrieving account", err)
botID = user.ID
discord.AddHandler(commandHandler)
discord.AddHandler(func(discord *discordgo.Session, ready *discordgo.Ready) {
err = discord.UpdateStatus(0, "A friendly helpful bot!")
if err != nil {
fmt.Println("Error attempting to set my status")
}
servers := discord.State.Guilds
fmt.Printf("GoBot has started on %d servers", len(servers))
})
err = discord.Open()
errCheck("Error opening connection to Discord", err)
defer discord.Close()
commandPrefix = "!"
<-make(chan struct{})
for _, be := range iface.AllBackends {
be.Setup()
}
for _, fe := range iface.AllFrontends {
fe.Setup()
}
// initialize global flags and default config
location := flag.String("location", "48.839661,2.375300", "`LOCATION` to be queried")
flag.StringVar(location, "l", "48.839661,2.375300", "`LOCATION` to be queried (shorthand)")
numdays := flag.Int("days", 1, "`NUMBER` of days of weather forecast to be displayed")
flag.IntVar(numdays, "d", 1, "`NUMBER` of days of weather forecast to be displayed (shorthand)")
unitSystem := flag.String("units", "metric", "`UNITSYSTEM` to use for output.\n \tChoices are: metric, imperial, si, metric-ms")
flag.StringVar(unitSystem, "u", "metric", "`UNITSYSTEM` to use for output. (shorthand)\n \tChoices are: metric, imperial, si, metric-ms")
selectedBackend := flag.String("backend", "forecast.io", "`BACKEND` to be used")
flag.StringVar(selectedBackend, "b", "forecast.io", "`BACKEND` to be used (shorthand)")
selectedFrontend := flag.String("frontend", "ascii-art-table", "`FRONTEND` to be used")
flag.StringVar(selectedFrontend, "f", "ascii-art-table", "`FRONTEND` to be used (shorthand)")
// print out a list of all backends and frontends in the usage
tmpUsage := flag.Usage
flag.Usage = func() {
tmpUsage()
pluginLists()
}
// read/write config and parse flags
if err := ingo.Parse("wego"); err != nil {
log.Fatalf("Error parsing config: %v", err)
}
// non-flag shortcut arguments overwrite possible flag arguments
for _, arg := range flag.Args() {
if v, err := strconv.Atoi(arg); err == nil && len(arg) == 1 {
*numdays = v
} else {
*location = arg
}
}
// get selected backend and fetch the weather data from it
be, ok := iface.AllBackends[*selectedBackend]
if !ok {
log.Fatalf("Could not find selected backend \"%s\"", *selectedBackend)
}
r := be.Fetch(*location, *numdays)
// set unit system
unit := iface.UnitsMetric
if *unitSystem == "imperial" {
unit = iface.UnitsImperial
} else if *unitSystem == "si" {
unit = iface.UnitsSi
} else if *unitSystem == "metric-ms" {
unit = iface.UnitsMetricMs
}
// get selected frontend and render the weather data with it
fe, ok := iface.AllFrontends[*selectedFrontend]
if !ok {
log.Fatalf("Could not find selected frontend \"%s\"", *selectedFrontend)
}
fe.Render(r, unit)
}
func errCheck(msg string, err error) {
if err != nil {
fmt.Printf("%s: %+v", msg, err)
panic(err)
}
}
func commandHandler(discord *discordgo.Session, message *discordgo.MessageCreate) {
user := message.Author
if user.ID == botID || user.Bot {
//Do nothing because the bot is talking
return
}
if message.Content == "test" {
discord.ChannelMessageSend(message.ChannelID, "test")
}
fmt.Printf("Message: %+v || From: %s\n", message.Message, message.Author)
}
So this does nothing, it just show the message that an user wrote in a channel. I saw that the print of the wego in the terminal is located in src/github.com/schachmat/frontends/emoji.go but i don't know how to use it with my bot, i'd like to print that when an user type "!weather".
I'm lost and don't know what to do.
Sorry for the english, i tried my best, and it's my first post on StackOverflow :)
using the BigQuery Golang sdk, how does one get a list of tables in a dataset that also contains their metadata?
package main
import (
"context"
"cloud.google.com/go/bigquery"
"google.golang.org/api/iterator"
"fmt"
"encoding/json"
)
func main() {
tables, metas, err := tableMetadatas(context.Background(), "my-project", "my-dataset")
if err != nil {
fmt.Println("Error: %v", err)
} else {
fmt.Println("Tables:")
bt, _ := json.MarshalIndent(tables, "", " ")
fmt.Println(string(bt))
fmt.Println("Table Metadatas:")
bm, _ := json.MarshalIndent(metas, "", " ")
fmt.Println(string(bm))
}
}
// Gets a list of Tables & Their respective Metadata in a Dataset
func tableMetadatas(ctx context.Context, project string, dataset string) ([]*bigquery.Table, []*bigquery.TableMetadata, error) {
c, err := bigquery.NewClient(ctx, project)
if err != nil {
return nil, nil, err
}
metas := make([]*bigquery.TableMetadata, 0)
tables := make([]*bigquery.Table, 0)
d := c.Dataset(dataset)
it := d.Tables(ctx)
for {
t, err := it.Next()
if err == iterator.Done {
break;
}
if err != nil {
return nil, nil, err
}
m, err := t.Metadata(ctx)
if err != nil {
return nil, nil, err
}
tables = append(tables, t)
metas = append(metas, m)
}
return tables, metas, nil
}
I'm having trouble getting parser.ParseDir to return any information in the returned struct.Imports field. parser.ParseFile does seem to work however. I would much rather use ParseDir than have to go through and call ParseFile on each file. Here's what I've tried:
package crawlers
import (
"fmt"
"go/parser"
"go/token"
"os"
"path/filepath"
)
type GoImports struct {
//imports []*string
}
//returns fullpaths
func (g *GoImports) Crawl(entry string) ([]*string, error) {
fp, err := filepath.Abs(entry)
if err != nil {
return nil, err
}
info, err := os.Stat(fp)
if err != nil {
return nil, err
}
if !info.IsDir() {
return nil, fmt.Errorf("Entry point should be a path to a directory of a go package.")
}
fset := token.NewFileSet()
//foo, err := parser.ParseDir(fset, fp, nil, parser.ImportsOnly)
//foo, err := parser.ParseDir(fset, "/Users/dowen00/local/go/src/got/crawlers/testdata/barpackage", nil, parser.ImportsOnly)
foo, err := parser.ParseDir(fset, "/Users/dowen00/local/go/src/opscli", func(os.FileInfo) bool { return true }, parser.ImportsOnly)
//_, err = parser.ParseDir(fset, "/Users/dowen00/local/go/src/got/crawlers/testdata", nil, parser.ImportsOnly)
//foo, err := parser.ParseFile(fset, "/Users/dowen00/local/go/src/got/crawlers/testdata/barpackage/bar.go", nil, parser.ImportsOnly)
if err != nil {
return nil, err
}
//fmt.Printf("%c\n", foo)
//fmt.Printf("%c\n", fset)
for k, v := range foo {
//for kv, vv := range v.Imports {
//fmt.Printf("%s: %c\n", kv, vv)
//}
fmt.Printf("%s: %c\n", k, v.Imports)
}
return nil, nil
}
What am I doing wrong?
I was able to work around the problem. If this, in fact, hasn't been implemented the go docs at https://golang.org/pkg/go/parser/#ParseDir should probably be revised to avoid wasting people's time.
Here's the work around:
for _, v := range foo {
//for kv, vv := range v.Imports {
//fmt.Printf("%s: %c\n", kv, vv)
//}
for kk, vv := range v.Files {
for _, i := range vv.Imports {
fmt.Printf("%s: %s\n", kk, i.Path.Value)
}
}
//fmt.Printf("%s: %c\n", k, v.Files)
}