Checkpoint. Code layout.
FossilOrigin-Name: 8a2deb5ee3deb752e25c82f50801529de8e28ec95dd9d4f292046bfc09f9dcd2
This commit is contained in:
parent
3583868771
commit
024b108bed
7 changed files with 330 additions and 51 deletions
|
|
@ -1,2 +1,3 @@
|
||||||
Session.vim
|
Session.vim
|
||||||
.cache/*
|
.cache/*
|
||||||
|
docs/*
|
||||||
|
|
|
||||||
6
Makefile
6
Makefile
|
|
@ -12,17 +12,19 @@ dependencies:
|
||||||
development: ${FILES}
|
development: ${FILES}
|
||||||
# can use gdb with this...
|
# can use gdb with this...
|
||||||
nim --debugInfo --assertions:on --linedir:on -d:testing -d:nimTypeNames --nimcache:.cache c ${FILES}
|
nim --debugInfo --assertions:on --linedir:on -d:testing -d:nimTypeNames --nimcache:.cache c ${FILES}
|
||||||
|
mv src/sieb .
|
||||||
|
|
||||||
debugger: ${FILES}
|
debugger: ${FILES}
|
||||||
nim --debugger:on --nimcache:.cache c ${FILES}
|
nim --debugger:on --nimcache:.cache c ${FILES}
|
||||||
|
mv src/sieb .
|
||||||
|
|
||||||
release:dependencies ${FILES}
|
release:dependencies ${FILES}
|
||||||
nim -d:release -d:nimDebugDlOpen --opt:speed --parallelBuild:0 --nimcache:.cache c ${FILES}
|
nim -d:release -d:strip --passc:-flto --opt:speed --nimcache:.cache c ${FILES}
|
||||||
mv src/sieb .
|
mv src/sieb .
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
nim doc ${FILES}
|
nim doc ${FILES}
|
||||||
#nim buildIndex ${FILES}
|
mv src/htmldocs docs
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
fossil clean --dotfiles -f -v
|
fossil clean --dotfiles -f -v
|
||||||
|
|
|
||||||
25
config.yml
25
config.yml
|
|
@ -2,36 +2,29 @@
|
||||||
# Example Sieb configuration file.
|
# Example Sieb configuration file.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
# Default, no logging. Relative to homedir.
|
# Default, no logging. Relative to homedir.
|
||||||
logfile: sieb.log
|
#logfile: sieb.log
|
||||||
|
|
||||||
## Filter message before rules
|
## Filter message before rules
|
||||||
#pre_filter:
|
pre_filter:
|
||||||
# - bogofilter
|
- bogofilter
|
||||||
|
|
||||||
## Filter message after rules
|
## Filter message after rules
|
||||||
#post_filter:
|
#post_filter:
|
||||||
# - bogofilter
|
# - bogofilter
|
||||||
|
|
||||||
rules:
|
|
||||||
-
|
|
||||||
headers:
|
|
||||||
woo: yeah
|
|
||||||
|
|
||||||
|
|
||||||
## Ordered, top down, first match wins.
|
## Ordered, top down, first match wins.
|
||||||
## Headers are lowercased. Multiple matches are AND'ed.
|
## Headers are lowercased. Multiple matches are AND'ed.
|
||||||
##
|
##
|
||||||
## Delivery default is ~/Maildir, any set value is an auto-created maildir under
|
## Delivery default is ~/Maildir, any set value is an auto-created maildir under
|
||||||
## that path.
|
## that path.
|
||||||
##
|
##
|
||||||
#rules:
|
rules:
|
||||||
# -
|
-
|
||||||
# headers:
|
headers:
|
||||||
# x-what: pcre-matcher
|
x-what: pcre-matcher
|
||||||
# poonie: pcre-matcher
|
poonie: pcre-matcher
|
||||||
# deliver: .whatever
|
deliver: .whatever
|
||||||
|
|
||||||
# # Magic "TO" which means To: OR Cc:
|
# # Magic "TO" which means To: OR Cc:
|
||||||
# -
|
# -
|
||||||
|
|
|
||||||
87
src/lib/config.nim
Normal file
87
src/lib/config.nim
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
# vim: set et nosta sw=4 ts=4 :
|
||||||
|
|
||||||
|
#
|
||||||
|
# Methods for finding and parsing sieb rules from YAML.
|
||||||
|
#
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# I M P O R T S
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
import
|
||||||
|
std/os,
|
||||||
|
std/streams,
|
||||||
|
std/strformat,
|
||||||
|
std/tables,
|
||||||
|
yaml/parser,
|
||||||
|
yaml/serialization
|
||||||
|
|
||||||
|
import util
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# C O N S T A N T S
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
const CONFFILES = @[
|
||||||
|
"/usr/local/etc/sieb/config.yml",
|
||||||
|
"/etc/sieb/config.yml"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# T Y P E S
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
type
|
||||||
|
rule = object
|
||||||
|
headers {.defaultVal: initTable[string, string]()}: Table[ string, string ]
|
||||||
|
deliver {.defaultVal: "Maildir"}: string
|
||||||
|
filter {.defaultVal: ""}: string
|
||||||
|
|
||||||
|
# Typed configuration file layout for YAML loading.
|
||||||
|
Config* = object
|
||||||
|
logfile {.defaultVal: "".}: string
|
||||||
|
pre_filter {.defaultVal: @[]}: seq[string]
|
||||||
|
post_filter {.defaultVal: @[]}: seq[string]
|
||||||
|
rules {.defaultVal: @[]}: seq[rule]
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# M E T H O D S
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
proc parse( path: string ): Config =
|
||||||
|
## Return a parsed configuration from yaml.
|
||||||
|
debug "Using configuration at: {path}".fmt
|
||||||
|
let stream = newFileStream( path )
|
||||||
|
try:
|
||||||
|
stream.load( result )
|
||||||
|
except YamlParserError as err:
|
||||||
|
debug err.msg
|
||||||
|
return Config() # return empty default, it could be "half parsed"
|
||||||
|
except YamlConstructionError as err:
|
||||||
|
debug err.msg
|
||||||
|
return Config()
|
||||||
|
finally:
|
||||||
|
stream.close
|
||||||
|
|
||||||
|
|
||||||
|
proc get_config*( path: string ): Config =
|
||||||
|
## Choose a configuration file for parsing, or if there are
|
||||||
|
## none available, return an empty config.
|
||||||
|
if path != "":
|
||||||
|
if not path.fileExists:
|
||||||
|
debug "Configfile \"{path}\" unreadable, ignoring.".fmt
|
||||||
|
return
|
||||||
|
return parse( path )
|
||||||
|
|
||||||
|
else:
|
||||||
|
# No explicit path given, walk the hardcoded paths to
|
||||||
|
# try and find one.
|
||||||
|
let homeconf = @[ getConfigDir() & "sieb/config.yml" ]
|
||||||
|
let configs = homeconf & CONFFILES
|
||||||
|
for conf in configs:
|
||||||
|
if conf.fileExists:
|
||||||
|
return parse( conf )
|
||||||
|
|
||||||
79
src/lib/maildir.nim
Normal file
79
src/lib/maildir.nim
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
# vim: set et nosta sw=4 ts=4 :
|
||||||
|
#
|
||||||
|
# A class that represents an individual Maildir.
|
||||||
|
#
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# I M P O R T S
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
import
|
||||||
|
std/os,
|
||||||
|
std/streams,
|
||||||
|
std/strformat,
|
||||||
|
std/times
|
||||||
|
|
||||||
|
import
|
||||||
|
util
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# T Y P E S
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
# A Maildir object.
|
||||||
|
#
|
||||||
|
type Maildir* = ref object
|
||||||
|
path*: string # Absolute path to the encapsualting dir
|
||||||
|
cur: string
|
||||||
|
new: string
|
||||||
|
tmp: string
|
||||||
|
|
||||||
|
# An email message, under a specific Maildir.
|
||||||
|
#
|
||||||
|
type Message* = ref object
|
||||||
|
dir: Maildir
|
||||||
|
path: string
|
||||||
|
stream: FileStream
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# M E T H O D S
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
proc newMaildir*( path: string ): Maildir =
|
||||||
|
## Create and return a new Maildir object, making it on-disk if necessary.
|
||||||
|
result = new Maildir
|
||||||
|
result.path = path
|
||||||
|
result.cur = path & "/cur"
|
||||||
|
result.new = path & "/new"
|
||||||
|
result.tmp = path & "/tmp"
|
||||||
|
|
||||||
|
if not dirExists( path ):
|
||||||
|
let perms = { fpUserExec, fpUserWrite, fpUserRead }
|
||||||
|
debug "Creating new maildir at {path}.".fmt
|
||||||
|
try:
|
||||||
|
for p in [ result.path, result.cur, result.new, result.tmp ]:
|
||||||
|
p.createDir
|
||||||
|
p.setFilePermissions( perms )
|
||||||
|
|
||||||
|
except CatchableError as err:
|
||||||
|
deferral "Unable to create Maildir: ({err.msg}), deferring delivery.".fmt
|
||||||
|
|
||||||
|
|
||||||
|
proc newMessage*( dir: Maildir ): Message =
|
||||||
|
## Create and return a Message - an open FileStream under a specific Maildir
|
||||||
|
## (in tmp)
|
||||||
|
result = new Message
|
||||||
|
|
||||||
|
let now = getTime()
|
||||||
|
result.dir = dir
|
||||||
|
echo now
|
||||||
|
|
||||||
|
# result.path = dir.path & now.seconds
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# make new message (tmp)
|
||||||
|
# save message (move from tmp to new)
|
||||||
|
|
||||||
115
src/lib/util.nim
Normal file
115
src/lib/util.nim
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
# vim: set et nosta sw=4 ts=4 :
|
||||||
|
#
|
||||||
|
# Various helper functions that don't have a better landing spot.
|
||||||
|
#
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# I M P O R T S
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
import
|
||||||
|
std/parseopt,
|
||||||
|
std/terminal,
|
||||||
|
std/strutils
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# C O N S T A N T S
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
const
|
||||||
|
VERSION = "v0.1.0"
|
||||||
|
USAGE = """
|
||||||
|
./sieb [-c] [-d] [-h] [-v]
|
||||||
|
|
||||||
|
-c --conf:
|
||||||
|
Use a specific configuration file. Otherwise, files are
|
||||||
|
attempted in the following order:
|
||||||
|
- ~/.config/sieb/config.yml
|
||||||
|
- /usr/local/etc/sieb/config.yml
|
||||||
|
- /etc/sieb/config.yml
|
||||||
|
|
||||||
|
-d --debug:
|
||||||
|
Debug: Be verbose while parsing.
|
||||||
|
|
||||||
|
-h --help:
|
||||||
|
Help. You're lookin' at it.
|
||||||
|
|
||||||
|
-v --version:
|
||||||
|
Display version number.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# T Y P E S
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
type Opts = object
|
||||||
|
config*: string # The path to an explicit configuration file.
|
||||||
|
debug*: bool # Explain what's being done.
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# M E T H O D S
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
proc hl( msg: string, fg: ForegroundColor, bright=false ): string =
|
||||||
|
## Quick wrapper for color formatting a string, since the 'terminal'
|
||||||
|
## module only deals with stdout directly.
|
||||||
|
if not isatty(stdout): return msg
|
||||||
|
|
||||||
|
var color: BiggestInt = ord( fg )
|
||||||
|
if bright: inc( color, 60 )
|
||||||
|
result = "\e[" & $color & 'm' & msg & "\e[0m"
|
||||||
|
|
||||||
|
|
||||||
|
proc deferral*( msg: string ) =
|
||||||
|
## Exit with Qmail deferral code immediately.
|
||||||
|
echo msg.replace( "\n", " - " ).hl( fgRed, bright=true )
|
||||||
|
quit( 1 )
|
||||||
|
|
||||||
|
|
||||||
|
proc debug*( msg: string ) =
|
||||||
|
## Emit +msg+ if debug mode is enabled.
|
||||||
|
if defined( testing ): echo msg
|
||||||
|
|
||||||
|
|
||||||
|
proc parse_cmdline*: Opts =
|
||||||
|
## Populate the opts object with the user's preferences.
|
||||||
|
|
||||||
|
# Config object defaults.
|
||||||
|
#
|
||||||
|
result = Opts(
|
||||||
|
config: "",
|
||||||
|
debug: false
|
||||||
|
)
|
||||||
|
|
||||||
|
# always set debug mode if development build.
|
||||||
|
result.debug = defined( testing )
|
||||||
|
|
||||||
|
for kind, key, val in getopt():
|
||||||
|
case kind
|
||||||
|
|
||||||
|
of cmdArgument:
|
||||||
|
discard
|
||||||
|
|
||||||
|
of cmdLongOption, cmdShortOption:
|
||||||
|
case key
|
||||||
|
of "conf", "c":
|
||||||
|
result.config = val
|
||||||
|
|
||||||
|
of "debug", "d":
|
||||||
|
result.debug = true
|
||||||
|
|
||||||
|
of "help", "h":
|
||||||
|
echo USAGE
|
||||||
|
quit( 0 )
|
||||||
|
|
||||||
|
of "version", "v":
|
||||||
|
echo "Sieb " & VERSION
|
||||||
|
quit( 0 )
|
||||||
|
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
of cmdEnd: assert( false ) # shouldn't reach here
|
||||||
|
|
||||||
|
|
||||||
46
src/sieb.nim
46
src/sieb.nim
|
|
@ -1,31 +1,33 @@
|
||||||
# vim: set et nosta sw=4 ts=4 :
|
# vim: set et nosta sw=4 ts=4 :
|
||||||
|
|
||||||
import
|
import
|
||||||
std/streams,
|
std/os,
|
||||||
yaml/serialization
|
std/streams
|
||||||
|
|
||||||
const
|
import
|
||||||
VERSION = "v0.1.0"
|
lib/config,
|
||||||
|
lib/maildir,
|
||||||
type
|
lib/util
|
||||||
rule = object
|
|
||||||
headers: seq[ tuple[ header: string, regexp: string ] ]
|
|
||||||
deliver {.defaultVal: "Maildir"}: string
|
|
||||||
filter {.defaultVal: ""}: string
|
|
||||||
|
|
||||||
# Typed configuration file layout for YAML loading.
|
|
||||||
Config = object
|
|
||||||
logfile {.defaultVal: "".}: string
|
|
||||||
pre_filter {.defaultVal: @[]}: seq[string]
|
|
||||||
post_filter {.defaultVal: @[]}: seq[string]
|
|
||||||
rules {.defaultVal: @[]}: seq[rule]
|
|
||||||
|
|
||||||
|
|
||||||
var conf: Config
|
if not existsEnv( "HOME" ):
|
||||||
|
deferral "Unable to determine HOME from environment."
|
||||||
|
|
||||||
let s = newFileStream( "config.yml" )
|
let
|
||||||
load( s, conf )
|
home = getHomeDir()
|
||||||
s.close
|
opts = parse_cmdline()
|
||||||
|
conf = get_config( opts.config )
|
||||||
|
default = newMaildir( home & "Maildir" )
|
||||||
|
|
||||||
echo conf
|
|
||||||
|
echo repr default.newMessage
|
||||||
|
|
||||||
|
# let input = stdin.newFileStream()
|
||||||
|
# var buf = input.readStr( 8192 )
|
||||||
|
# var message = buf
|
||||||
|
# while buf != "":
|
||||||
|
# buf = input.readStr( 8192 )
|
||||||
|
# message = message & buf
|
||||||
|
|
||||||
|
# echo message
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue