mirror of
https://github.com/ChronosX88/yans.git
synced 2024-11-24 12:32:17 +00:00
Implement wildmat support
This commit is contained in:
parent
dda82b7916
commit
7ae9a7af35
@ -6,6 +6,7 @@
|
||||
|
||||
### Features
|
||||
|
||||
- :heavy_check_mark: Wildmat support
|
||||
- :heavy_check_mark: Database (SQLite)
|
||||
- :construction: Articles posting
|
||||
- :x: Transit mode
|
||||
|
5
go.mod
5
go.mod
@ -10,4 +10,7 @@ require (
|
||||
github.com/pressly/goose/v3 v3.5.0
|
||||
)
|
||||
|
||||
require github.com/pkg/errors v0.9.1 // indirect
|
||||
require (
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
)
|
||||
|
2
go.sum
2
go.sum
@ -40,6 +40,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/docker/cli v20.10.8+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
|
@ -1,10 +1,14 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"github.com/ChronosX88/yans/internal/config"
|
||||
"github.com/ChronosX88/yans/internal/models"
|
||||
"github.com/ChronosX88/yans/internal/utils"
|
||||
"github.com/dlclark/regexp2"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/mattn/go-sqlite3"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/pressly/goose/v3"
|
||||
)
|
||||
@ -16,8 +20,19 @@ type SQLiteBackend struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func regexHelper(re, s string) (bool, error) {
|
||||
return regexp2.MustCompile(re, regexp2.None).MatchString(s)
|
||||
}
|
||||
|
||||
func NewSQLiteBackend(cfg config.SQLiteBackendConfig) (*SQLiteBackend, error) {
|
||||
db, err := sqlx.Open("sqlite3", cfg.Path)
|
||||
sql.Register("sqlite3_with_regexp",
|
||||
&sqlite3.SQLiteDriver{
|
||||
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
|
||||
return conn.RegisterFunc("regexp", regexHelper, true)
|
||||
},
|
||||
})
|
||||
|
||||
db, err := sqlx.Open("sqlite3_with_regexp", cfg.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -41,6 +56,19 @@ func (sb *SQLiteBackend) ListGroups() ([]models.Group, error) {
|
||||
return groups, sb.db.Select(&groups, "SELECT * FROM groups")
|
||||
}
|
||||
|
||||
func (sb *SQLiteBackend) ListGroupsByPattern(pattern string) ([]models.Group, error) {
|
||||
var groups []models.Group
|
||||
w, err := utils.ParseWildmat(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := w.ToRegex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return groups, sb.db.Select(&groups, "SELECT * FROM groups WHERE group_name REGEXP ?", r.String())
|
||||
}
|
||||
|
||||
func (sb *SQLiteBackend) GetArticlesCount(g models.Group) (int, error) {
|
||||
var count int
|
||||
return count, sb.db.Get(&count, "SELECT COUNT(*) FROM articles_to_groups WHERE group_id = ?", g.ID)
|
||||
|
@ -8,6 +8,7 @@ const (
|
||||
|
||||
type StorageBackend interface {
|
||||
ListGroups() ([]models.Group, error)
|
||||
ListGroupsByPattern(pattern string) ([]models.Group, error)
|
||||
GetArticlesCount(g models.Group) (int, error)
|
||||
GetGroupLowWaterMark(g models.Group) (int, error)
|
||||
GetGroupHighWaterMark(g models.Group) (int, error)
|
||||
|
@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ChronosX88/yans/internal/backend"
|
||||
"github.com/ChronosX88/yans/internal/models"
|
||||
"github.com/ChronosX88/yans/internal/protocol"
|
||||
"strings"
|
||||
"time"
|
||||
@ -60,13 +61,20 @@ func (h *Handler) handleList(s *Session, arguments []string, id uint) error {
|
||||
fallthrough
|
||||
case "ACTIVE":
|
||||
{
|
||||
groups, err := h.backend.ListGroups()
|
||||
var groups []models.Group
|
||||
var err error
|
||||
if len(arguments) == 2 {
|
||||
groups, err = h.backend.ListGroupsByPattern(arguments[1])
|
||||
} else {
|
||||
groups, err = h.backend.ListGroups()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sb.Write([]byte(protocol.MessageListOfNewsgroupsFollows + protocol.CRLF))
|
||||
for _, v := range groups {
|
||||
// TODO set high/low mark and posting status to actual values
|
||||
// TODO set actual post permission status
|
||||
c, err := h.backend.GetArticlesCount(v)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -82,16 +90,24 @@ func (h *Handler) handleList(s *Session, arguments []string, id uint) error {
|
||||
}
|
||||
sb.Write([]byte(fmt.Sprintf("%s %d %d n"+protocol.CRLF, v.GroupName, highWaterMark, lowWaterMark)))
|
||||
} else {
|
||||
sb.Write([]byte(fmt.Sprintf("%s 0 0 n"+protocol.CRLF, v.GroupName)))
|
||||
sb.Write([]byte(fmt.Sprintf("%s 0 1 n"+protocol.CRLF, v.GroupName)))
|
||||
}
|
||||
}
|
||||
}
|
||||
case "NEWSGROUPS":
|
||||
{
|
||||
groups, err := h.backend.ListGroups()
|
||||
var groups []models.Group
|
||||
var err error
|
||||
if len(arguments) == 2 {
|
||||
groups, err = h.backend.ListGroupsByPattern(arguments[1])
|
||||
} else {
|
||||
groups, err = h.backend.ListGroups()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sb.Write([]byte(protocol.MessageListOfNewsgroupsFollows + protocol.CRLF))
|
||||
for _, v := range groups {
|
||||
desc := ""
|
||||
if v.Description == nil {
|
||||
|
83
internal/utils/wildmat.go
Normal file
83
internal/utils/wildmat.go
Normal file
@ -0,0 +1,83 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dlclark/regexp2"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Wildmat struct {
|
||||
patterns []*WildmatPattern
|
||||
}
|
||||
|
||||
type WildmatPattern struct {
|
||||
negated bool
|
||||
pattern string
|
||||
regex *regexp2.Regexp
|
||||
}
|
||||
|
||||
func regexpEscape(str string) string {
|
||||
restrictedChars := []string{"(\\", "+", "|", "{", "[", "(", ")", "^", "$", ".", "#"}
|
||||
for _, v := range restrictedChars {
|
||||
str = strings.ReplaceAll(str, v, "\\"+v)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func convertWildmatToRegex(pat string) (*regexp2.Regexp, error) {
|
||||
regex := ""
|
||||
pat = regexpEscape(pat)
|
||||
patRunes := []rune(pat)
|
||||
for _, v := range patRunes {
|
||||
switch v {
|
||||
case '?':
|
||||
regex += "."
|
||||
case '*':
|
||||
regex += ".?"
|
||||
default:
|
||||
{
|
||||
regex += string(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return regexp2.Compile(regex, regexp2.None)
|
||||
}
|
||||
|
||||
func ParseWildmat(wildmat string) (*Wildmat, error) {
|
||||
res := &Wildmat{}
|
||||
for _, v := range strings.Split(wildmat, ",") {
|
||||
if len(v) > 0 && v[0] == '!' {
|
||||
r, err := convertWildmatToRegex(v[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.patterns = append(res.patterns, &WildmatPattern{pattern: v[1:], negated: true, regex: r})
|
||||
} else {
|
||||
r, err := convertWildmatToRegex(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.patterns = append(res.patterns, &WildmatPattern{pattern: v, negated: false, regex: r})
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (w *Wildmat) ToRegex() (*regexp2.Regexp, error) {
|
||||
res := "(%s)%s"
|
||||
include := ""
|
||||
exclude := ""
|
||||
for _, v := range w.patterns {
|
||||
if v.negated {
|
||||
exclude += fmt.Sprintf("(?!%s)", v.regex.String())
|
||||
} else {
|
||||
if len(include) != 0 {
|
||||
include += fmt.Sprintf("|(%s)", v.regex.String())
|
||||
} else {
|
||||
include += fmt.Sprintf("(%s)", v.regex.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
res = fmt.Sprintf(res, include, exclude)
|
||||
return regexp2.Compile(res, regexp2.None)
|
||||
}
|
Loading…
Reference in New Issue
Block a user