From 024b108bed7c4b8d0b9d8ba332225fac07f32ec6 Mon Sep 17 00:00:00 2001 From: mahlon Date: Sun, 18 Jun 2023 02:37:25 +0000 Subject: [PATCH] Checkpoint. Code layout. FossilOrigin-Name: 8a2deb5ee3deb752e25c82f50801529de8e28ec95dd9d4f292046bfc09f9dcd2 --- .fossil-settings/ignore-glob | 1 + Makefile | 6 +- config.yml | 47 ++++++-------- src/lib/config.nim | 87 ++++++++++++++++++++++++++ src/lib/maildir.nim | 79 ++++++++++++++++++++++++ src/lib/util.nim | 115 +++++++++++++++++++++++++++++++++++ src/sieb.nim | 46 +++++++------- 7 files changed, 330 insertions(+), 51 deletions(-) create mode 100644 src/lib/config.nim create mode 100644 src/lib/maildir.nim create mode 100644 src/lib/util.nim diff --git a/.fossil-settings/ignore-glob b/.fossil-settings/ignore-glob index 1e08f4a..c71393c 100644 --- a/.fossil-settings/ignore-glob +++ b/.fossil-settings/ignore-glob @@ -1,2 +1,3 @@ Session.vim .cache/* +docs/* diff --git a/Makefile b/Makefile index fb251b5..51ffed3 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/config.yml b/config.yml index c123664..95dd2bf 100644 --- a/config.yml +++ b/config.yml @@ -2,47 +2,40 @@ # 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: -# - -# headers: -# TO: regexp -# deliver: .whereever - -# # Filter message through reformail, then deliver to ~/Maildir. -# - -# headers: -# x-what: fuckery -# filter: reformail ... + # # Magic "TO" which means To: OR Cc: + # - + # headers: + # TO: regexp + # deliver: .whereever + + # # Filter message through reformail, then deliver to ~/Maildir. + # - + # headers: + # x-what: fuckery + # filter: reformail ... diff --git a/src/lib/config.nim b/src/lib/config.nim new file mode 100644 index 0000000..30ad634 --- /dev/null +++ b/src/lib/config.nim @@ -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 ) + diff --git a/src/lib/maildir.nim b/src/lib/maildir.nim new file mode 100644 index 0000000..7a0a741 --- /dev/null +++ b/src/lib/maildir.nim @@ -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) + diff --git a/src/lib/util.nim b/src/lib/util.nim new file mode 100644 index 0000000..7825497 --- /dev/null +++ b/src/lib/util.nim @@ -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 + + diff --git a/src/sieb.nim b/src/sieb.nim index 5eab23f..9cf03fe 100644 --- a/src/sieb.nim +++ b/src/sieb.nim @@ -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