Checkpoint. Code layout.

FossilOrigin-Name: 8a2deb5ee3deb752e25c82f50801529de8e28ec95dd9d4f292046bfc09f9dcd2
This commit is contained in:
Mahlon E. Smith 2023-06-18 02:37:25 +00:00
parent 3583868771
commit 024b108bed
7 changed files with 330 additions and 51 deletions

View file

@ -1,2 +1,3 @@
Session.vim
.cache/*
docs/*

View file

@ -12,17 +12,19 @@ dependencies:
development: ${FILES}
# can use gdb with this...
nim --debugInfo --assertions:on --linedir:on -d:testing -d:nimTypeNames --nimcache:.cache c ${FILES}
mv src/sieb .
debugger: ${FILES}
nim --debugger:on --nimcache:.cache c ${FILES}
mv src/sieb .
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 .
docs:
nim doc ${FILES}
#nim buildIndex ${FILES}
mv src/htmldocs docs
clean:
fossil clean --dotfiles -f -v

View file

@ -2,36 +2,29 @@
# Example Sieb configuration file.
#
# Default, no logging. Relative to homedir.
logfile: sieb.log
#logfile: sieb.log
## Filter message before rules
#pre_filter:
# - bogofilter
pre_filter:
- bogofilter
## Filter message after rules
#post_filter:
# - bogofilter
rules:
-
headers:
woo: yeah
## Ordered, top down, first match wins.
## Headers are lowercased. Multiple matches are AND'ed.
##
## Delivery default is ~/Maildir, any set value is an auto-created maildir under
## that path.
##
#rules:
# -
# headers:
# x-what: pcre-matcher
# poonie: pcre-matcher
# deliver: .whatever
rules:
-
headers:
x-what: pcre-matcher
poonie: pcre-matcher
deliver: .whatever
# # Magic "TO" which means To: OR Cc:
# -

87
src/lib/config.nim Normal file
View 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
View 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
View 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

View file

@ -1,31 +1,33 @@
# vim: set et nosta sw=4 ts=4 :
import
std/streams,
yaml/serialization
std/os,
std/streams
const
VERSION = "v0.1.0"
type
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]
import
lib/config,
lib/maildir,
lib/util
var conf: Config
if not existsEnv( "HOME" ):
deferral "Unable to determine HOME from environment."
let s = newFileStream( "config.yml" )
load( s, conf )
s.close
let
home = getHomeDir()
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