Skip to content

Commit b0324d9

Browse files
committed
ADD: support for validating feeds (RSS/Atom/JsonFeed)
1 parent e593896 commit b0324d9

File tree

4 files changed

+257
-3
lines changed

4 files changed

+257
-3
lines changed

Diff for: go.mod

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
module github.com/FileFormatInfo/fflint
22

3-
go 1.18
3+
go 1.22
44

55
require (
66
github.com/JoshVarga/svgparser v0.0.0-20200804023048-5eaba627a7d1
77
github.com/adrg/frontmatter v0.2.0
88
github.com/antchfx/xmlquery v1.3.3
99
github.com/bmatcuk/doublestar/v4 v4.0.2
10+
github.com/cevaris/ordered_map v0.0.0-20220813181356-34664b69742b
1011
github.com/cheggaaa/pb/v3 v3.0.8
1112
github.com/mattn/go-isatty v0.0.18
1213
github.com/mitchellh/go-homedir v1.1.0
14+
github.com/mmcdole/gofeed v1.3.0
1315
github.com/muesli/mango-cobra v1.2.0
1416
github.com/muesli/roff v0.1.0
1517
github.com/olekukonko/tablewriter v0.0.5
@@ -22,22 +24,28 @@ require (
2224
github.com/zyxar/image2ascii v0.0.0-20180912034614-460a04e371ae
2325
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
2426
golang.org/x/net v0.14.0
27+
gopkg.in/yaml.v2 v2.4.0
2528
gopkg.in/yaml.v3 v3.0.1
2629
)
2730

2831
require (
2932
github.com/BurntSushi/toml v1.2.1 // indirect
33+
github.com/PuerkitoBio/goquery v1.8.0 // indirect
3034
github.com/VividCortex/ewma v1.1.1 // indirect
35+
github.com/andybalholm/cascadia v1.3.1 // indirect
3136
github.com/antchfx/xpath v1.2.0 // indirect
32-
github.com/cevaris/ordered_map v0.0.0-20220813181356-34664b69742b // indirect
3337
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
3438
github.com/fatih/color v1.13.0 // indirect
3539
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
3640
github.com/inconshreveable/mousetrap v1.1.0 // indirect
41+
github.com/json-iterator/go v1.1.12 // indirect
3742
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
3843
github.com/kr/pretty v0.3.1 // indirect
3944
github.com/mattn/go-colorable v0.1.13 // indirect
4045
github.com/mattn/go-runewidth v0.0.14 // indirect
46+
github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 // indirect
47+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
48+
github.com/modern-go/reflect2 v1.0.2 // indirect
4149
github.com/muesli/mango v0.1.0 // indirect
4250
github.com/muesli/mango-pflag v0.1.0 // indirect
4351
github.com/rivo/uniseg v0.4.2 // indirect
@@ -46,5 +54,4 @@ require (
4654
golang.org/x/sys v0.11.0 // indirect
4755
golang.org/x/text v0.12.0 // indirect
4856
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
49-
gopkg.in/yaml.v2 v2.4.0 // indirect
5057
)

Diff for: go.sum

+23
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak
33
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
44
github.com/JoshVarga/svgparser v0.0.0-20200804023048-5eaba627a7d1 h1:RAQocNl+YQYGPt5yh4SR5zFUIHKrXnLhjIGhHO4Vwnc=
55
github.com/JoshVarga/svgparser v0.0.0-20200804023048-5eaba627a7d1/go.mod h1:tMmgUTWcco9d1ZmK7zjxuTv7XWZhyutXIsgu0uJ3gDw=
6+
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
7+
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
68
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
79
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
810
github.com/adrg/frontmatter v0.2.0 h1:/DgnNe82o03riBd1S+ZDjd43wAmC6W35q67NHeLkPd4=
911
github.com/adrg/frontmatter v0.2.0/go.mod h1:93rQCj3z3ZlwyxxpQioRKC1wDLto4aXHrbqIsnH9wmE=
12+
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
13+
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
1014
github.com/antchfx/xmlquery v1.3.3 h1:HYmadPG0uz8CySdL68rB4DCLKXz2PurCjS3mnkVF4CQ=
1115
github.com/antchfx/xmlquery v1.3.3/go.mod h1:64w0Xesg2sTaawIdNqMB+7qaW/bSqkQm+ssPaCMWNnc=
1216
github.com/antchfx/xpath v1.1.10/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
@@ -30,8 +34,11 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF
3034
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
3135
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
3236
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
37+
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
3338
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
3439
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
40+
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
41+
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
3542
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
3643
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
3744
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -56,6 +63,15 @@ github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWV
5663
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
5764
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
5865
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
66+
github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4=
67+
github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE=
68+
github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 h1:Zr92CAlFhy2gL+V1F+EyIuzbQNbSgP4xhTODZtrXUtk=
69+
github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8=
70+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
71+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
72+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
73+
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
74+
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
5975
github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI=
6076
github.com/muesli/mango v0.1.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4=
6177
github.com/muesli/mango-cobra v1.2.0 h1:DQvjzAM0PMZr85Iv9LIMaYISpTOliMEg+uMFtNbYvWg=
@@ -91,6 +107,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
91107
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
92108
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
93109
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
110+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
94111
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
95112
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
96113
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
@@ -103,22 +120,28 @@ golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZ
103120
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
104121
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
105122
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
123+
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
106124
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
107125
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
108126
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
109127
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
110128
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
111129
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
112130
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
131+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
113132
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
133+
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
114134
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
115135
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
116136
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
117137
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
118138
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
139+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
119140
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
141+
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
120142
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
121143
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
144+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
122145
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
123146
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
124147
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

Diff for: internal/command/all.go

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import "github.com/spf13/cobra"
55
func AddAllCommands(rootCmd *cobra.Command) {
66

77
AddExtCommand(rootCmd)
8+
AddFeedCommand(rootCmd)
89
AddFrontmatterCommand(rootCmd)
910
AddHtmlCommand(rootCmd)
1011
AddIcoCommand(rootCmd)

Diff for: internal/command/feed.go

+223
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
package command
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"net/url"
7+
8+
"github.com/FileFormatInfo/fflint/internal/argtype"
9+
"github.com/FileFormatInfo/fflint/internal/shared"
10+
"github.com/mmcdole/gofeed"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
var (
15+
feedFormat = argtype.NewStringSet("Feed file format", "auto", []string{"atom", "auto", "jsonfeed", "rss"})
16+
feedStrict bool
17+
)
18+
19+
// xmlCmd represents the xml command
20+
var feedCmd = &cobra.Command{
21+
Args: cobra.MinimumNArgs(1),
22+
Use: "feed [options] files...",
23+
Short: "Validate feeds (RSS/Atom/Jsonfeed)",
24+
Long: `Checks that your feeds are valid. RSS, Atom and JSONFeed are supported.`,
25+
PreRunE: feedInit,
26+
RunE: shared.MakeFileCommand(feedCheck),
27+
PostRunE: feedCleanup,
28+
}
29+
30+
func AddFeedCommand(rootCmd *cobra.Command) {
31+
rootCmd.AddCommand(feedCmd)
32+
feedCmd.Flags().Var(&feedFormat, "format", feedFormat.HelpText())
33+
feedCmd.Flags().BoolVar(&feedStrict, "strict", true, "Check contents in addition to parsability")
34+
}
35+
36+
func feedCheck(f *shared.FileContext) {
37+
38+
data, readErr := f.ReadFile()
39+
if readErr != nil {
40+
f.RecordResult("fileRead", false, map[string]interface{}{
41+
"error": readErr,
42+
})
43+
return
44+
}
45+
var parseErr error
46+
var feed *gofeed.Feed
47+
48+
detectedFeedType := gofeed.DetectFeedType(bytes.NewReader(data))
49+
if feedFormat.String() == "auto" {
50+
if detectedFeedType == gofeed.FeedTypeUnknown {
51+
f.RecordResult("feedDetectType", false, map[string]interface{}{
52+
"error": "Unknown feed type",
53+
"startOfInput": Substr(string(data), 0, 100),
54+
})
55+
return
56+
}
57+
} else if (feedFormat.String() == "rss" && detectedFeedType != gofeed.FeedTypeRSS) ||
58+
(feedFormat.String() == "atom" && detectedFeedType != gofeed.FeedTypeAtom) ||
59+
(feedFormat.String() == "jsonfeed" && detectedFeedType != gofeed.FeedTypeJSON) {
60+
f.RecordResult("feedTypeMismatch", false, map[string]interface{}{
61+
"error": "Feed type mismatch",
62+
"detected": detectedFeedType,
63+
"expected": feedFormat.String(),
64+
})
65+
return
66+
}
67+
68+
p := gofeed.NewParser()
69+
feed, parseErr = p.Parse(bytes.NewReader(data))
70+
71+
if parseErr != nil {
72+
f.RecordResult("feedParse", false, map[string]interface{}{
73+
"error": parseErr,
74+
})
75+
return
76+
}
77+
78+
if feed == nil {
79+
f.RecordResult("feedEmpty", false, map[string]interface{}{
80+
"error": "Empty feed",
81+
})
82+
return
83+
}
84+
85+
if !feedStrict {
86+
return
87+
}
88+
89+
if feed.Title == "" {
90+
f.RecordResult("feedTitle", false, map[string]interface{}{
91+
"error": "Missing title",
92+
})
93+
}
94+
95+
if feed.Description == "" {
96+
f.RecordResult("feedTitle", false, map[string]interface{}{
97+
"error": "Missing title",
98+
})
99+
}
100+
101+
if feedParentLinkErr := IsValidUrl(feed.Link); feedParentLinkErr != nil {
102+
f.RecordResult("feedParentLink", false, map[string]interface{}{
103+
"error": feedParentLinkErr,
104+
"url": feed.Link,
105+
})
106+
}
107+
108+
if feedSelfLinkErr := IsValidUrl(feed.FeedLink); feedSelfLinkErr != nil {
109+
f.RecordResult("feedSelfLink", false, map[string]interface{}{
110+
"error": feedSelfLinkErr,
111+
"url": feed.FeedLink,
112+
})
113+
}
114+
115+
if feed.Updated != "" && feed.UpdatedParsed == nil {
116+
f.RecordResult("feedUpdated", false, map[string]interface{}{
117+
"error": "Invalid updated date",
118+
"rawdate": feed.Updated,
119+
})
120+
}
121+
122+
if feed.Published != "" && feed.PublishedParsed == nil {
123+
f.RecordResult("feedPublished", false, map[string]interface{}{
124+
"error": "Invalid published date",
125+
"rawdate": feed.Published,
126+
})
127+
}
128+
129+
if feed.Items == nil || len(feed.Items) == 0 {
130+
f.RecordResult("feedItems", false, map[string]interface{}{
131+
"error": "No items found",
132+
})
133+
} else {
134+
guidMap := make(map[string]int)
135+
for i, item := range feed.Items {
136+
if item.Title == "" {
137+
f.RecordResult("feedItemTitle", false, map[string]interface{}{
138+
"error": "Missing title",
139+
"index": i,
140+
})
141+
}
142+
if item.Description == "" {
143+
f.RecordResult("feedItemDescription", false, map[string]interface{}{
144+
"error": "Missing description",
145+
"index": i,
146+
})
147+
}
148+
if item.Link == "" {
149+
f.RecordResult("feedItemLink", false, map[string]interface{}{
150+
"error": "Missing link",
151+
"index": i,
152+
})
153+
}
154+
if item.Published != "" && item.PublishedParsed == nil {
155+
f.RecordResult("feedItemPublished", false, map[string]interface{}{
156+
"error": "Invalid published date",
157+
"index": i,
158+
"rawdate": item.Published,
159+
})
160+
}
161+
if item.Updated != "" && item.UpdatedParsed == nil {
162+
f.RecordResult("feedItemUpdated", false, map[string]interface{}{
163+
"error": "Invalid updated date",
164+
"index": i,
165+
"rawdate": item.Updated,
166+
})
167+
}
168+
if item.GUID == "" {
169+
f.RecordResult("feedItemGUID", false, map[string]interface{}{
170+
"error": "Missing GUID",
171+
"index": i,
172+
})
173+
} else if originalIndex, ok := guidMap[item.GUID]; ok {
174+
f.RecordResult("feedItemGUID", false, map[string]interface{}{
175+
"error": "Duplicate GUID",
176+
"originalIndex": originalIndex,
177+
"duplicateIndex": i,
178+
"guid": item.GUID,
179+
})
180+
} else {
181+
guidMap[item.GUID] = i
182+
}
183+
}
184+
}
185+
}
186+
187+
func feedInit(cmd *cobra.Command, args []string) error {
188+
return nil
189+
}
190+
191+
func feedCleanup(cmd *cobra.Command, args []string) error {
192+
return nil
193+
}
194+
195+
func IsValidUrl(target string) error {
196+
if target == "" {
197+
return fmt.Errorf("URL not set")
198+
}
199+
_, err := url.ParseRequestURI(target)
200+
if err != nil {
201+
return err
202+
}
203+
return nil
204+
}
205+
206+
// UTF8-safe substring
207+
func Substr(input string, start int, length int) string {
208+
209+
if start == 0 && length >= len(input) {
210+
return input
211+
}
212+
213+
asRunes := []rune(input)
214+
if start >= len(asRunes) {
215+
return ""
216+
}
217+
218+
if start+length > len(asRunes) {
219+
length = len(asRunes) - start
220+
}
221+
222+
return string(asRunes[start : start+length])
223+
}

0 commit comments

Comments
 (0)