Compare commits

..

No commits in common. "48143595bfb022a5f58e3724df7a4201711102fc" and "28948e513fb714bd2598ccc07039393197fed429" have entirely different histories.

25 changed files with 108 additions and 87 deletions

View file

@ -1 +1 @@
2.5 2.4

View file

@ -1,13 +1,7 @@
# Ruby-Ezmlm # Ruby-Ezmlm
* home: code
* http://code.martini.nu/ruby-ezmlm : https://bitbucket.org/mahlon/Ruby-Ezmlm
* mirrors:
* https://github.com/mahlonsmith/ruby-ezmlm
* https://hg.sr.ht/~mahlon/ruby-ezmlm
* Ezmlm:
* http://untroubled.org/ezmlm/
## Authors ## Authors
@ -24,6 +18,8 @@ manager for use with the Qmail MTA, and the messages contained therein.
(The -idx provides an extended feature set over the original ezmlm (The -idx provides an extended feature set over the original ezmlm
environment.) environment.)
http://untroubled.org/ezmlm/
This was tested against ezmlm-idx 7.2.2. This was tested against ezmlm-idx 7.2.2.
*Strong recommendation*: Create your lists with archiving (-a) and *Strong recommendation*: Create your lists with archiving (-a) and
@ -50,7 +46,7 @@ be a generic interface for parsing and browsing list content.
## Limitations ## Limitations
This library doesn't create new lists from scratch. Use `ezmlm-make`. This library doesn't create new lists from scratch. Use ezmlm-make.
This library is designed to only work with lists stored on disk (the This library is designed to only work with lists stored on disk (the
default), not the SQL backends. default), not the SQL backends.

View file

@ -23,7 +23,6 @@ $version = ( LIBDIR + "#{PROJECT}.rb" ).read.split(/\n/).
select{|line| line =~ /VERSION =/}.first.match(/([\d|.]+)/)[1] select{|line| line =~ /VERSION =/}.first.match(/([\d|.]+)/)[1]
task :default => [ :spec, :docs, :package ] task :default => [ :spec, :docs, :package ]
task :spec => [ :compile ]
######################################################################## ########################################################################
@ -34,8 +33,12 @@ require 'rubygems'
require 'rubygems/package_task' require 'rubygems/package_task'
spec = Gem::Specification.new do |s| spec = Gem::Specification.new do |s|
s.email = 'mahlon@martini.nu' s.email = 'mahlon@martini.nu'
s.homepage = 'https://bitbucket.org/mahlon/ruby-ezmlm' s.homepage = 'https://bitbucket.org/mahlon/Ruby-Ezmlm'
s.authors = [ 'Mahlon E. Smith', 'Michael Granger', 'Jeremiah Jordan' ] s.authors = [
'Mahlon E. Smith <mahlon@martini.nu>',
'Michael Granger <ged@faeriemud.org>',
'Jeremiah Jordan <jeremiah.m.jordan@gmail.com>'
]
s.platform = Gem::Platform::RUBY s.platform = Gem::Platform::RUBY
s.summary = "Interact with Ezmlm-IDX mailing lists." s.summary = "Interact with Ezmlm-IDX mailing lists."
s.name = PROJECT s.name = PROJECT
@ -81,7 +84,7 @@ begin
rdoc.rdoc_dir = 'docs' rdoc.rdoc_dir = 'docs'
rdoc.main = "README.rdoc" rdoc.main = "README.rdoc"
# rdoc.options = [ '-f', 'fivefish' ] # rdoc.options = [ '-f', 'fivefish' ]
rdoc.rdoc_files = [ 'lib', *FileList['ext/**.c'], *FileList['*.rdoc'], *FileList['*.md'] ] rdoc.rdoc_files = [ 'lib', *FileList['ext/*/*.c'], *FileList['*.rdoc'] ]
end end
RDoc::Task.new do |rdoc| RDoc::Task.new do |rdoc|

View file

@ -10,14 +10,14 @@ Examples
-------- --------
### Print the list address for all lists in a directory: *Print the list address for all lists in a directory*:
Ezmlm.each_list( '/lists' ) do |list| Ezmlm.each_list( '/lists' ) do |list|
puts list.address puts list.address
end end
### Check if I'm subscribed to a list, and if so, unsubscribe: *Check if I'm subscribed to a list, and if so, unsubscribe*:
(You don't really have to check first, subscribe and unsubscribe are (You don't really have to check first, subscribe and unsubscribe are
idempotent.) idempotent.)
@ -31,14 +31,14 @@ idempotent.)
puts "The list now has %d subscribers!" % [ list.subscribers.size ] puts "The list now has %d subscribers!" % [ list.subscribers.size ]
### Iterate over the subscriber list: *Iterate over the subscriber list*:
list.subscribers.each do |subscriber| list.subscribers.each do |subscriber|
# ... # ...
end end
### Make the list moderated, and add a moderator: *Make the list moderated, and add a moderator*:
list.moderated = true list.moderated = true
list.add_moderator( 'mahlon@martini.nu' ) list.add_moderator( 'mahlon@martini.nu' )
@ -48,7 +48,7 @@ All other list behavior tunables operate in a similar fashion, see RDoc
for details. for details.
### Archiving! *Archiving!*
All of the archival pieces take advantage of Ezmlm-IDX extensions. All of the archival pieces take advantage of Ezmlm-IDX extensions.
If you want to use these features, you'll want to enable archiving If you want to use these features, you'll want to enable archiving
@ -67,12 +67,12 @@ rebuild the necessary files - afterwards, they are kept up to date
automatically. automatically.
### How many messages are in the archive?: *How many messages are in the archive?*:
list.message_count #=> 123 list.message_count #=> 123
### Fetch message number 100 from the archive: *Fetch message number 100 from the archive*:
message = list.message( 100 ) or abort "No such message." message = list.message( 100 ) or abort "No such message."
@ -96,7 +96,7 @@ Mikel Lindsaar (https://github.com/mikel/mail). See its documentation
for specifics. for specifics.
### Iterate over messages in a specific thread: *Iterate over messages in a specific thread*:
Messages know what thread they belong to. Once you have a thread object Messages know what thread they belong to. Once you have a thread object
from a message, it is an enumerable. Iterate or sort on it using from a message, it is an enumerable. Iterate or sort on it using
@ -110,7 +110,7 @@ Threads are also aware of who participated in the conversation, via the
'authors' and 'each_author' methods. 'authors' and 'each_author' methods.
### Iterate over messages from a specific author: *Iterate over messages from a specific author:*
Messages know who authored them. Once you have an author object from a Messages know who authored them. Once you have an author object from a
message, it is an enumerable. Iterate or sort on it using standard Ruby message, it is an enumerable. Iterate or sort on it using standard Ruby

View file

@ -13,7 +13,6 @@
* *
*/ */
static
void surf(unsigned int out[8],const unsigned int in[12],const unsigned int seed[32]) void surf(unsigned int out[8],const unsigned int in[12],const unsigned int seed[32])
{ {
unsigned int t[12]; unsigned int x; unsigned int sum = 0; unsigned int t[12]; unsigned int x; unsigned int sum = 0;
@ -33,7 +32,6 @@ void surf(unsigned int out[8],const unsigned int in[12],const unsigned int seed[
} }
} }
static
void surfpcs_init(surfpcs *s,const unsigned int k[32]) void surfpcs_init(surfpcs *s,const unsigned int k[32])
{ {
int i; int i;
@ -43,7 +41,6 @@ void surfpcs_init(surfpcs *s,const unsigned int k[32])
s->todo = 0; s->todo = 0;
} }
static
void surfpcs_add(surfpcs *s,const char *x,unsigned int n) void surfpcs_add(surfpcs *s,const char *x,unsigned int n)
{ {
int i; int i;
@ -62,7 +59,6 @@ void surfpcs_add(surfpcs *s,const char *x,unsigned int n)
} }
} }
static
void surfpcs_addlc(surfpcs *s,const char *x,unsigned int n) void surfpcs_addlc(surfpcs *s,const char *x,unsigned int n)
/* modified from surfpcs_add by case-independence and skipping ' ' & '\t' */ /* modified from surfpcs_add by case-independence and skipping ' ' & '\t' */
{ {
@ -88,7 +84,6 @@ void surfpcs_addlc(surfpcs *s,const char *x,unsigned int n)
} }
} }
static
void surfpcs_out(surfpcs *s,unsigned char h[32]) void surfpcs_out(surfpcs *s,unsigned char h[32])
{ {
int i; int i;
@ -100,7 +95,6 @@ void surfpcs_out(surfpcs *s,unsigned char h[32])
for (i = 0;i < 32;++i) h[i] = outdata[end[i]]; for (i = 0;i < 32;++i) h[i] = outdata[end[i]];
} }
static
void makehash(const char *indata,unsigned int inlen,char *hash) void makehash(const char *indata,unsigned int inlen,char *hash)
/* makes hash[COOKIE=20] from stralloc *indata, ignoring case and */ /* makes hash[COOKIE=20] from stralloc *indata, ignoring case and */
/* SPACE/TAB */ /* SPACE/TAB */
@ -118,7 +112,6 @@ void makehash(const char *indata,unsigned int inlen,char *hash)
hash[i] = 'a' + (h[i] & 15); hash[i] = 'a' + (h[i] & 15);
} }
static
unsigned int subhashb(const char *s,long len) unsigned int subhashb(const char *s,long len)
{ {
unsigned long h; unsigned long h;
@ -128,7 +121,6 @@ unsigned int subhashb(const char *s,long len)
return h % 53; return h % 53;
} }
static
unsigned int subhashs(const char *s) unsigned int subhashs(const char *s)
{ {
return subhashb(s,strlen(s)); return subhashb(s,strlen(s));
@ -140,7 +132,7 @@ unsigned int subhashs(const char *s)
/* /*
* call-seq: * call­seq:
* Ezmlm::Hash.address( email ) -> String * Ezmlm::Hash.address( email ) -> String
* *
* Call the Surf hashing function on an +email+ address, returning * Call the Surf hashing function on an +email+ address, returning
@ -149,7 +141,7 @@ unsigned int subhashs(const char *s)
* the '<' character.) * the '<' character.)
* *
*/ */
static VALUE VALUE
address( VALUE klass, VALUE email ) { address( VALUE klass, VALUE email ) {
char hash[20]; char hash[20];
char *input; char *input;
@ -166,14 +158,14 @@ address( VALUE klass, VALUE email ) {
/* /*
* call-seq: * call­seq:
* Ezmlm::Hash.subscriber( address ) -> String * Ezmlm::Hash.subscriber( address ) -> String
* *
* Call the subscriber hashing function on an email +address+, returning * Call the subscriber hashing function on an email +address+, returning
* the index character referring to the file containing subscriber presence. * the index character referring to the file containing subscriber presence.
* *
*/ */
static VALUE VALUE
subscriber( VALUE klass, VALUE email ) { subscriber( VALUE klass, VALUE email ) {
unsigned int prefix; unsigned int prefix;

View file

@ -27,19 +27,19 @@ static const unsigned int littleendian[8] = {
#define data ((unsigned char *) s->in) #define data ((unsigned char *) s->in)
#define outdata ((unsigned char *) s->out) #define outdata ((unsigned char *) s->out)
static void surf( unsigned int out[8], const unsigned int in[12], const unsigned int seed[32] ); extern void surf( unsigned int out[8], const unsigned int in[12], const unsigned int seed[32] );
static void surfpcs_init( surfpcs *s, const unsigned int k[32] ); extern void surfpcs_init( surfpcs *s, const unsigned int k[32] );
static void surfpcs_add( surfpcs *s, const char *x,unsigned int n ); extern void surfpcs_add( surfpcs *s, const char *x,unsigned int n );
static void surfpcs_addlc( surfpcs *s, const char *x,unsigned int n ); extern void surfpcs_addlc( surfpcs *s, const char *x,unsigned int n );
static void surfpcs_out( surfpcs *s, unsigned char h[32] ); extern void surfpcs_out( surfpcs *s, unsigned char h[32] );
#endif #endif
#ifndef SUBHASH_H #ifndef SUBHASH_H
#define SUBHASH_H #define SUBHASH_H
static unsigned int subhashs(const char *s); unsigned int subhashs(const char *s);
static unsigned int subhashb(const char *s,long len); unsigned int subhashb(const char *s,long len);
#define subhashsa(SA) subhashb((SA)->s,(SA)->len) #define subhashsa(SA) subhashb((SA)->s,(SA)->len)
#endif #endif

View file

@ -1,6 +1,5 @@
# vim: set nosta noet ts=4 sw=4: # vim: set nosta noet ts=4 sw=4:
require 'pathname'
# A Ruby interface to the ezmlm-idx mailing list system. # A Ruby interface to the ezmlm-idx mailing list system.
# #
@ -10,11 +9,24 @@ require 'pathname'
# puts "\"%s\" <%s>" % [ list.name, list.address ] # puts "\"%s\" <%s>" % [ list.name, list.address ]
# end # end
# #
#
# == Version
#
# $Id$
#
#---
#
# Please see the file LICENSE in the base directory for licensing details.
#
require 'pathname'
### Toplevel namespace module
module Ezmlm module Ezmlm
# $Id$
# Package version # Package version
VERSION = '1.1.2' VERSION = '1.0.0'
# Suck in the components. # Suck in the components.
# #
@ -35,7 +47,7 @@ module Ezmlm
def find_directories( listsdir ) def find_directories( listsdir )
listsdir = Pathname.new( listsdir ) listsdir = Pathname.new( listsdir )
return Pathname.glob( listsdir + '*' ).sort.select do |entry| return Pathname.glob( listsdir + '*' ).sort.select do |entry|
entry.directory? && ( entry + 'ezmlmrc' ).exist? entry.directory? && ( entry + 'mailinglist' ).exist?
end end
end end

View file

@ -1,19 +1,27 @@
#!/usr/bin/ruby #!/usr/bin/ruby
# vim: set nosta noet ts=4 sw=4: # vim: set nosta noet ts=4 sw=4:
# A Ruby interface to a single Ezmlm-idx mailing list directory.
#
# list = Ezmlm::List.new( '/path/to/listdir' )
#
#
# == Version
#
# $Id$
#
#---
require 'pathname' require 'pathname'
require 'time' require 'time'
require 'etc' require 'etc'
require 'ezmlm' unless defined?( Ezmlm ) require 'ezmlm' unless defined?( Ezmlm )
# A Ruby interface to a single Ezmlm-idx mailing list directory. ### A Ruby interface to an ezmlm-idx mailing list directory
# ###
# list = Ezmlm::List.new( '/path/to/listdir' )
#
#---
class Ezmlm::List class Ezmlm::List
# $Id: list.rb,v a89d91d4b157 2017/06/23 17:54:26 mahlon $
# Valid subdirectories/sections for subscriptions. # Valid subdirectories/sections for subscriptions.
SUBSCRIPTION_DIRS = %w[ deny mod digest allow ] SUBSCRIPTION_DIRS = %w[ deny mod digest allow ]
@ -24,7 +32,7 @@ class Ezmlm::List
### ###
def initialize( listdir ) def initialize( listdir )
listdir = Pathname.new( listdir ) unless listdir.is_a?( Pathname ) listdir = Pathname.new( listdir ) unless listdir.is_a?( Pathname )
unless listdir.directory? && ( listdir + 'ezmlmrc' ).exist? unless listdir.directory? && ( listdir + 'mailinglist' ).exist?
raise ArgumentError, "%p doesn't appear to be an ezmlm-idx list." % [ listdir.to_s ] raise ArgumentError, "%p doesn't appear to be an ezmlm-idx list." % [ listdir.to_s ]
end end
@listdir = listdir @listdir = listdir
@ -69,7 +77,7 @@ class Ezmlm::List
### Returns +true+ if +address+ is a subscriber to this list. ### Returns +true+ if +address+ is a subscriber to this list.
### ###
def include?( addr, section: nil ) def include?( addr, section: nil )
addr = addr.downcase addr.downcase!
file = self.subscription_dir( section ) + Ezmlm::Hash.subscriber( addr ) file = self.subscription_dir( section ) + Ezmlm::Hash.subscriber( addr )
return false unless file.exist? return false unless file.exist?
return file.read.scan( /T([^\0]+)\0/ ).flatten.include?( addr ) return file.read.scan( /T([^\0]+)\0/ ).flatten.include?( addr )
@ -90,7 +98,7 @@ class Ezmlm::List
def subscribe( *addr, section: nil ) def subscribe( *addr, section: nil )
addr.each do |address| addr.each do |address|
next unless address.index( '@' ) next unless address.index( '@' )
address = address.downcase address.downcase!
file = self.subscription_dir( section ) + Ezmlm::Hash.subscriber( address ) file = self.subscription_dir( section ) + Ezmlm::Hash.subscriber( address )
self.with_safety do self.with_safety do
@ -116,7 +124,7 @@ class Ezmlm::List
### ###
def unsubscribe( *addr, section: nil ) def unsubscribe( *addr, section: nil )
addr.each do |address| addr.each do |address|
address = address.downcase address.downcase!
file = self.subscription_dir( section ) + Ezmlm::Hash.subscriber( address ) file = self.subscription_dir( section ) + Ezmlm::Hash.subscriber( address )
self.with_safety do self.with_safety do
@ -663,16 +671,6 @@ class Ezmlm::List
end end
### Return a Time object for the last activity on the list, or nil
### if archiving is disabled or there are no posts.
###
def last_activity
file = self.listdir + 'archnum'
return unless file.exist?
return file.stat.mtime
end
### Parse all thread indexes into a single array that can be used ### Parse all thread indexes into a single array that can be used
### as a lookup table. ### as a lookup table.
### ###
@ -823,11 +821,13 @@ class Ezmlm::List
### ###
def with_safety( &block ) def with_safety( &block )
home = self.homedir home = self.homedir
home.chmod( home.stat.mode | 01000 ) # enable sticky mode = home.stat.mode
home.chmod( mode | 01000 ) # enable sticky
yield yield
ensure ensure
home.chmod( home.stat.mode & ~01000 ) # disable sticky home.chmod( mode )
end end
end # class Ezmlm::List end # class Ezmlm::List

View file

@ -1,9 +1,6 @@
#!/usr/bin/ruby #!/usr/bin/ruby
# vim: set nosta noet ts=4 sw=4: # vim: set nosta noet ts=4 sw=4:
require 'pathname'
require 'ezmlm' unless defined?( Ezmlm )
# A collection of messages authored from a unique user. # A collection of messages authored from a unique user.
# #
@ -14,9 +11,20 @@ require 'ezmlm' unless defined?( Ezmlm )
# author.name #=> "Help - navigate on interface?" # author.name #=> "Help - navigate on interface?"
# author.first.date.to_s #=> "2017-05-07T14:55:05-07:00" # author.first.date.to_s #=> "2017-05-07T14:55:05-07:00"
# #
#
# == Version
#
# $Id$
#
#--- #---
require 'pathname'
require 'ezmlm' unless defined?( Ezmlm )
### A collection of messages for a specific author.
###
class Ezmlm::List::Author class Ezmlm::List::Author
# $Id$
include Enumerable include Enumerable
### Instantiate a new list of messages given ### Instantiate a new list of messages given

View file

@ -1,9 +1,6 @@
#!/usr/bin/ruby #!/usr/bin/ruby
# vim: set nosta noet ts=4 sw=4: # vim: set nosta noet ts=4 sw=4:
require 'pathname'
require 'ezmlm' unless defined?( Ezmlm )
require 'mail'
# An individual list message. # An individual list message.
# #
@ -15,9 +12,20 @@ require 'mail'
# This class passes all heavy lifting to the Mail::Message library. # This class passes all heavy lifting to the Mail::Message library.
# Please see it for specifics on usage. # Please see it for specifics on usage.
# #
# == Version
#
# $Id$
#
#--- #---
require 'pathname'
require 'ezmlm' unless defined?( Ezmlm )
require 'mail'
### A Ruby interface to an individual list message.
###
class Ezmlm::List::Message class Ezmlm::List::Message
# $Id$
### Instantiate a new messag from a +list+ and a +message_number+. ### Instantiate a new messag from a +list+ and a +message_number+.
### ###

View file

@ -1,9 +1,6 @@
#!/usr/bin/ruby #!/usr/bin/ruby
# vim: set nosta noet ts=4 sw=4: # vim: set nosta noet ts=4 sw=4:
require 'pathname'
require 'ezmlm' unless defined?( Ezmlm )
# A collection of messages for a specific archive thread. # A collection of messages for a specific archive thread.
# #
@ -11,9 +8,20 @@ require 'ezmlm' unless defined?( Ezmlm )
# thread.subject #=> "Help - navigate on interface?" # thread.subject #=> "Help - navigate on interface?"
# thread.first.date.to_s #=> "2017-05-07T14:55:05-07:00" # thread.first.date.to_s #=> "2017-05-07T14:55:05-07:00"
# #
#
# == Version
#
# $Id$
#
#--- #---
require 'pathname'
require 'ezmlm' unless defined?( Ezmlm )
### A collection of messages for a specific archive thread.
###
class Ezmlm::List::Thread class Ezmlm::List::Thread
# $Id$
include Enumerable include Enumerable
### Instantiate a new thread of messages given ### Instantiate a new thread of messages given

View file

@ -236,10 +236,6 @@ describe Ezmlm::List do
expect( list.digest_count ).to eq( 10 ) expect( list.digest_count ).to eq( 10 )
end end
it 'knows the date of the most recent posting' do
expect( list.last_activity ).to be_a( Time )
end
it 'can set a new digest message count' do it 'can set a new digest message count' do
list.digest_count = 25 list.digest_count = 25
expect( list.digest_count ).to eq( 25 ) expect( list.digest_count ).to eq( 25 )

View file

@ -3,7 +3,6 @@
require 'simplecov' if ENV['COVERAGE'] require 'simplecov' if ENV['COVERAGE']
require 'rspec' require 'rspec'
require 'fileutils' require 'fileutils'
require 'tmpdir'
require_relative '../lib/ezmlm' require_relative '../lib/ezmlm'
@ -31,8 +30,7 @@ module SpecHelpers
### Create a copy of a fresh listdir into /tmp. ### Create a copy of a fresh listdir into /tmp.
### ###
def make_listdir def make_listdir
dirname = "%s/%s.%d.%0.4f" % [ dirname = "/tmp/%s.%d.%0.4f" % [
Dir.tmpdir,
'ezmlm_list', 'ezmlm_list',
Process.pid, Process.pid,
(Time.now.to_f % 3600), (Time.now.to_f % 3600),