<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Gray Soft / Tags / Community</title>
  <id>tag:graysoftinc.com,2014-03-20:/tags/Community</id>
  <updated>2014-09-19T14:52:52Z</updated>
  <link rel="self" href="http://graysoftinc.com/tags/Community/feed.xml"/>
  <link rel="alternate" href="http://graysoftinc.com/tags/Community"/>
  <author>
    <name>James Edward Gray II</name>
  </author>
  <entry>
    <title>Asking Better Questions</title>
    <link rel="alternate" href="http://graysoftinc.com/rusting/asking-better-questions"/>
    <id>tag:graysoftinc.com,2014-08-21:/posts/123</id>
    <updated>2014-09-19T14:52:52Z</updated>
    <summary>I often forget that I can get better help when I take the time to ask smarter questions.</summary>
    <content type="html">&lt;p&gt;I've been playing with some Rust lately and learning a bunch of new concepts.&lt;/p&gt;

&lt;p&gt;As part of my experimentation, I built &lt;a href="https://gist.github.com/JEG2/f57212afa30bd7a48d7c"&gt;an RPN calculator in the language&lt;/a&gt;, just as an exercise that would require a moderate amount of code to be worked out.  Honestly, I don't fully understand everything that I had to do to get this code working yet.  Maybe 20% of it came about as me following instructions from what seem to be very helpful compiler errors.&lt;/p&gt;

&lt;p&gt;I wanted to start attacking these concepts that I didn't understand to increase my knowledge.  Of course, I was impatient.  I had some working code and I knew what I was missing, so I jumped into &lt;a href="http://chat.mibbit.com/?server=irc.mozilla.org&amp;amp;channel=%23rust"&gt;IRC&lt;/a&gt;, pointed at the code, and asked some not-at-all complete questions.  All I got was crickets.&lt;/p&gt;

&lt;p&gt;This isn't a failing of the Rust community.  It's a lesson I have to relearn every now and then.  You have to take the time to present a good enough question that answering it is easy enough and worth it.  It's hard work to get your head around 100 lines of code and, even if you do, you'll still be missing plenty of context if the question isn't really well formed.  Given that, most people just ignore the question.  That's probably for the better too, because any answers provided likely would have missed the points I really needed help with.&lt;/p&gt;

&lt;p&gt;What it did do was remind me that I needed to ask better questions.  Today I took the time to build a small example that addressed one of my key confusions with Rust so far:  Lifetimes (plus Ownership, Borrowing, and References it turns out).  Here's the code I whipped up:&lt;/p&gt;

&lt;div class="highlight highlight-rust"&gt;&lt;pre&gt;&lt;span class="c1"&gt;// I'm trying to make sure I have my head around "lifetimes"&lt;/span&gt;
&lt;span class="c1"&gt;// after a few readings of the guide:&lt;/span&gt;
&lt;span class="c1"&gt;// http://doc.rust-lang.org/guide-lifetimes.html.  The following is&lt;/span&gt;
&lt;span class="c1"&gt;// a simple example of what I think I now know that I'll try to explain&lt;/span&gt;
&lt;span class="c1"&gt;// in this comment.&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// Because the letters are "borrowed references," (Do I have my terms right?)&lt;/span&gt;
&lt;span class="c1"&gt;// they could potentially be freed before the Lists struct that contains them.&lt;/span&gt;
&lt;span class="c1"&gt;// Since this possibility exists, Rust requires the explicit lifetime 'a which&lt;/span&gt;
&lt;span class="c1"&gt;// indicates that the lifetime of a created Lists instance is tied to whatever&lt;/span&gt;
&lt;span class="c1"&gt;// references were passed into the letters slot.&lt;/span&gt;

&lt;span class="cp"&gt;#[deriving(Show)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Lists&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;letters&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;lists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Lists&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt;&lt;span class="p"&gt;]};&lt;/span&gt;
    &lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lists&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I made &lt;a href="https://gist.github.com/JEG2/ae7746544cef14be1606"&gt;a Gist&lt;/a&gt; out of that and then went back to IRC to ask people how correct my current understanding was.  The effort really paid off.  I got some great clarifications:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;JEG2
  Can anyone tell me if my understanding of the explicit lifetime
  in this example, as explained by my comment, feels correct?
  https://gist.github.com/JEG2/ae7746544cef14be1606
…
kimundi
  JEG2: That sounds right
…
steveklabnik
  JEG2: oh hey!!!!
  JEG2: good to see you here :)
  &amp;lt;3
Sharp
  JEG2: It's very close to being right (I *think*). Lists and letters
  must have the same lifetime, period. It's not that Lists acquires
  the lifetime of letters when it's created.
kimundi
  JEG2: Not that in this example you are using string literals,
  which have 'static lifetime and thus the references are always valid
JEG2
  steveklabnik: Hey steve. Thanks.
steveklabnik
  JEG2: the lifetime guide is _terrible_ btw, and as soon as my guide
  is wrapped up, which should be this week, it's next on the re-write train
…
kimundi
  Sharp: Eh, that doesn't sound right
…
JEG2
  steveklabnik: Good to know. Thanks.
…
Sharp
  kimundi: Well, actually I think Lists is bound to live at most
  as long as any one of its lifetime parameters. So yeah, you're right.
  I am clearly still confused about lifetimes :P
kimundi
  yeah, that sounds more correct :)
  No, wait
…
kimundi
  Eh, nevermind, its correct. List can not live longer as the 'a lifetime
  because else it would not be able to be created.
…
Dr-Emann
  JEG2: minor thing: everything has a lifetime, whether it
  contains references or not. Rust doesn't just create one
  when there's a possibility of premature frees. It only
  makes you be explicit with them in those cases though.
…
JEG2
  Sharp and Dr-Emann: Good clarifications on lifetimes. Thank you.
Sharp
  You're welcome, glad you could get something out of my confusion :)
…
steveklabnik
  JEG2: i left a comment on your gist too
Dr-Emann
  JEG2: of note is that even though the lifetime of the references
  is defined as the same as the lifetime of the containing struct,
  you can store any reference which lives _at least_ as long as the struct.
steveklabnik
  JEG2: make that two comments
…
JEG2
  Dr-Emann: Ah, I did not know that. So it's legal for my reference
  to outlive the struct?
…
Dr-Emann
  JEG2: they do, in your case, even. You pass literal strings,
  which are references into read only memory in the executable itself.
  They live for the entirety of the program.
…
JEG2
  Dr-Emann: Gotcha.
Dr-Emann
  JEG2: they have the special lifetime 'static. But you can also
  make a string on the stack and include references into it,
  as long as your struct doesn't outlive the String.
JEG2
  steveklabnik: Thanks for the comments. I'm getting a more complete picture.
…
steveklabnik
  JEG2: awesome. I -really really- want to make sure we explain this well,
  but we're not quite there yet.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As Steve Klabnik said, &lt;a href="https://gist.github.com/JEG2/ae7746544cef14be1606#comment-1286037"&gt;he also posted some very helpful comments on my Gist&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
In other words, I got quite a bit of help.  That help is also on public IRC logs and Gists, so it might even be of use to others.&lt;/p&gt;

&lt;p&gt;It really pays to spend some time developing your question asking skills and then working on remembering to put those skills to use.&lt;/p&gt;</content>
    <author>
      <name>James Edward Gray II</name>
    </author>
  </entry>
  <entry>
    <title>Dave Thomas is Definitely the Sammy Sosa of Programming</title>
    <link rel="alternate" href="http://graysoftinc.com/my-heroes/dave-thomas-is-definitely-the-sammy-sosa-of-programming"/>
    <id>tag:graysoftinc.com,2008-01-04:/posts/43</id>
    <updated>2014-04-05T16:22:22Z</updated>
    <summary>Just a few details I wanted to share about my favorite home run hitter.</summary>
    <content type="html">&lt;p&gt;There is a debate raging in the Ruby community that I don't want any part of.  I'm not going to engage in any of the mud slinging and there will be no debate here.  Commenters have been warned.  What I do want to do is to share some simple uncontested facts about a man I am lucky enough to know.&lt;/p&gt;

&lt;p&gt;Sammy Sosa is famous for one thing:  hitting home runs.  In the entire history of the game of baseball five players have managed to hit over 600 home runs and Sammy is one of them.  If that wasn't amazing enough, he has hit at least one home run against every single Major League team and in 44 Major League ballparks.  Baseball fans everywhere love to watch Sammy Sosa at bat.&lt;/p&gt;

&lt;p&gt;Now if I had to name five programmers who get me as excited about programming as Sammy Sosa does about baseball, Dave Thomas would definitely make the list.  Dave does exactly what Sammy always does:  continually preforms the hardest tasks of his profession while making it look easy to the fans.  Allow me to give a few examples.&lt;/p&gt;

&lt;p&gt;Dave Thomas was one of the authors of the &lt;a href="http://agilemanifesto.org/"&gt;Agile Manifesto&lt;/a&gt;.  I think we tend to forget just how significant that is.  If you are a programmer who doesn't spend his day in discussions with 50 coworkers, trying to satisfy the needs of various committees, or reading endless specifications of expected behavior, the odds are good that you have been affected by this document.  I, for one, am ecstatic that he helped spare me from that fate.  Of course, he didn't stop there.&lt;/p&gt;

&lt;p&gt;The Agile Manifesto gave us the theory, but we still needed to develop the practices it embodied.  Many have done that for us, including Dave.  He was the coauthor of a book called &lt;a href="http://pragmaticprogrammer.com/the-pragmatic-programmer"&gt;&lt;em&gt;The Pragmatic Programmer:  From Journeyman to Master&lt;/em&gt;&lt;/a&gt;.  If you are a programmer who hasn't read this book, you owe it to yourself to pick it up.  It gave so much great advice that it made me a better person in addition to a better programmer.  I've lost count of how many times I've told someone to "Make Stone Soup" or "Fix Broken Windows."  If the Agile Manifesto was a home run, this book was a grand slam.&lt;/p&gt;

&lt;p&gt;Then there's Ruby.  You can quite literally say that one man lead the western world in the charge to discover the Ruby programming language.  He found the language, encouraged other programmers to try it out, cowrote &lt;a href="http://ruby-doc.org/docs/ProgrammingRuby/"&gt;the first English book about it&lt;/a&gt;, literally gave that book away for free, designed a documentation tool for the language, and contributed all the documentation he had created for that first book back to the source code.  That was long before most of us had even heard of Ruby.  These days &lt;a href="http://www.pragprog.com/titles/ruby3"&gt;that book is going into its third edition&lt;/a&gt; and that same man has coauthored &lt;a href="http://www.pragprog.com/titles/rails2"&gt;a book about Ruby's most popular framework, Ruby on Rails&lt;/a&gt;.  It should be little surprise that Dave did all of that.&lt;/p&gt;

&lt;p&gt;Giving us Ruby wasn't enough though, Dave had to teach us how to use it too.  It would be a quite a challenge to count the number of conference speeches he has given and workshops he has led.  Some of those workshops even raised large amounts of money for multiple charities, just because Dave is also a nice guy.  He also cofounded his own &lt;a href="http://www.pragprog.com/"&gt;publishing company&lt;/a&gt; and sought out authors to write great new titles on all things programming.  That publishing company has become a trend setter for all of the technical publishers, popularizing concepts like PDF-only distribution and "Beta Books."&lt;/p&gt;

&lt;p&gt;Those are just some of the bigger things I can tell you that Dave Thomas has done for programming.  I can't even begin to guess at the scope of his smaller deeds.  For example, many know that I now maintain &lt;a href="/the-gateway/what-is-the-ruby-talk-gateway"&gt;the Ruby Talk to comp.lang.ruby gateway&lt;/a&gt;, but take a wild guess at who built it in the first place…  Yeah, that's the guy.&lt;/p&gt;

&lt;p&gt;Dave, all I can say is, keep knocking 'em out of the park.  I'll be the fan watching from the stands, eating pistachio nuts, and cheering you on.&lt;/p&gt;</content>
    <author>
      <name>James Edward Gray II</name>
    </author>
  </entry>
  <entry>
    <title>I Enjoy the Regional Conferences</title>
    <link rel="alternate" href="http://graysoftinc.com/conferences/i-enjoy-the-regional-conferences"/>
    <id>tag:graysoftinc.com,2007-09-10:/posts/40</id>
    <updated>2014-04-05T14:43:55Z</updated>
    <summary>After spending the weekend at the Lone Star Rubyconf, I'm recommending others attend their regional conferences.</summary>
    <content type="html">&lt;p&gt;I have to say that the Lone Star Rubyconf was just great.  I'm hoping that's representative of the other regional Ruby conferences as well and from what I've heard it is.&lt;/p&gt;

&lt;p&gt;I was at the official Rubyconf last year and I'm comfortable saying that the Lone Star Rubyconf competed well on content.  We had great keynotes from Charles Nutter and Zed Shaw (though he's wrong about that don't love your language point); we had presentations from icons of the community, like Hal Fulton; we had popular topics covered by the experts, like the RSpec presentation from Mr. RSpec, David Chelimsky; and we had the wonderfully practical technical talks, like Evan Short's smooth coverage of Domain Specific Languages.&lt;/p&gt;

&lt;p&gt;While you do have to consider it a small minus to miss seeing Matz and a few other key Rubyists, the conference countered with a terrific small community feel.  Everyone was open and friendly.  You could easily approach anyone and chat them up about nearly any topic.  Several people approached me and I loved it.&lt;/p&gt;

&lt;p&gt;Oh, and you can say "y'all" in an LSRC talk without getting funny looks.  You can't beat that.&lt;/p&gt;

&lt;p&gt;So, if you're like me and can't make the official conference this year, consider attending a regional conference instead.  I had a very positive experience and I look forward to next year's offering.&lt;/p&gt;</content>
    <author>
      <name>James Edward Gray II</name>
    </author>
  </entry>
  <entry>
    <title>The Ruby VM:  Episode I</title>
    <link rel="alternate" href="http://graysoftinc.com/the-ruby-vm-interview/the-ruby-vm-episode-i"/>
    <id>tag:graysoftinc.com,2007-02-16:/posts/29</id>
    <updated>2014-04-04T20:29:56Z</updated>
    <summary>This first interview covers who I'm talking with, what we are talking about, and when we will see the results of their labor.</summary>
    <content type="html">&lt;p&gt;&lt;strong&gt;Hello and thank you both for agreeing to answer my questions.  To begin, would you please introduce yourselves and tell us about your role in Ruby's development?&lt;/strong&gt;&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;
&lt;strong&gt;Matz&lt;/strong&gt;:&lt;/dt&gt;
  &lt;dd&gt;
    &lt;p&gt;
      I am the designer and the first implementer of the Ruby language.  My
      real name is Yukihiro Matsumoto, that sounds something like
      You-Key-Hero Matz-Motor in English.  But it's too long to remember and
      pronounce, so just call me Matz.
    &lt;/p&gt;

    &lt;p&gt;
      I have been developing Ruby since 1993.  It is now quite complicated
      and has performance problem.  I have had vague plan of rewriting the
      interpreter for long time, but I have never been motivated enough to
      throw out the current interpreter and start developing new one.
    &lt;/p&gt;

    &lt;p&gt;
      Then Koichi came in with YARV that seemed to have much brighter future
      than my vaporware - it runs - so I asked him to take a role of the
      official implementer of the core.  Although I enjoy both designing and
      implementation of the language, I don't think I am gifted for language
      implementation.  So I thought that it might be the time to focus on
      designing when I saw YARV.
    &lt;/p&gt;
  &lt;dd&gt;
      
  &lt;/dd&gt;
&lt;/dd&gt;
&lt;dt&gt;
&lt;strong&gt;ko1&lt;/strong&gt;:&lt;/dt&gt;
  &lt;dd&gt;
    &lt;p&gt;
      Thank you for your interest in YARV and me.  BTW, I'm thinking what "YARV"
      stand for.  Because it is not Yet Another.  Someone proposed that "YARV
      ain't RubyVM".  If YARV means "YARV ain't RubyVM", what is YARV?
    &lt;/p&gt;

    &lt;p&gt;
      I'm Koichi Sasada.  Koichi is given name, and "ichi" means "one" in
      Japanese.  So I use "ko1" as my nick.  I'm an assistant at Department
      (...snip...) of Tokyo.  My research interest is systems software,
      especially operating system, programming language, parallel systems, and
      so on.  And I'm a member of Nihon Ruby no Kai (Ruby Association in Japan).
      I plan(ed) some Ruby events like
      &lt;a href="http://jp.rubyist.net/RubyKaigi2007/english.html"&gt;RubyKaigi&lt;/a&gt;
      and am an editor of
      &lt;a href="http://jp.rubyist.net/magazine"&gt;Rubyist Magazine&lt;/a&gt;.  I also
      develop(ed) &lt;a href="http://www.atdot.net/nadoka/"&gt;Nadoka&lt;/a&gt;,
      &lt;a href="http://www.namikilab.tuat.ac.jp/~sasada/prog/rava2.html"&gt;Rava&lt;/a&gt;,
      &lt;a href="http://www.namikilab.tuat.ac.jp/~sasada/prog/rucheme.html"&gt;Rucheme&lt;/a&gt;,
      and some projects.  Say, I'm a developer of YARV: Yet Another RubyVM.
    &lt;/p&gt;

    &lt;p&gt;
      My role in Ruby's development?  To steal VM hacking pleasure from Matz?
    &lt;/p&gt;
  &lt;dd&gt;
&lt;/dd&gt;
&lt;/dd&gt;
&lt;/dl&gt;&lt;p&gt;&lt;strong&gt;The point of this interview is to talk about the future of Ruby's interpreter.  To start that, can you please explain what YARV/Rite is?  How is it different in design from the old Ruby interpreter?&lt;/strong&gt;&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;
&lt;strong&gt;Matz&lt;/strong&gt;:&lt;/dt&gt;
  &lt;dd&gt;
    &lt;p&gt;
      I have always been more interested in designing the language than
      implementing it.  So Ruby interpreter is always slower than it should
      be.  I think I pruned all low-hanging fruits, so that it seemed to
      required to re-implement whole core to achieve performance boost.  I
      planned a new interpreter code-named 'Rite' in 2001 or so, but I have
      never motivated enough to start the project.  Maybe I had been too
      busy, or perhaps too lazy.
    &lt;/p&gt;

    &lt;p&gt;
      Then, Koichi came in, and showed us his YARV.  Many had tried
      implementing Ruby interpreter in the past, but no one but Koichi
      reached that level of implemented feature set (at the time; now we
      have JRuby and RubyCLR both compatible with Ruby 1.8).  So I asked him
      to take part in the development of the new core, and he agreed.
    &lt;/p&gt;

    &lt;p&gt;
      January 1st 2007, he checked in YARV in to the trunk of our
      repository, so it is now official core of the Ruby 1.9.  I am still
      working on old implementation in matzruby branch.  Since it is easier
      for me to experiment new language features on the old interpreter, but
      I will eventually switch to the new engine.
    &lt;/p&gt;

    &lt;p&gt;
      For YARV implementation detail, Koichi will explain.
    &lt;/p&gt;

    &lt;p&gt;&lt;strong&gt;Does this mean we are leaving the name Rite behind and keeping YARV?  Or will YARV be renamed at some point?&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;
      The name Rite will not be used for this generation of the language,
      unless Koichi ask me.  I am not sure Koichi is going to keep YARV, or
      not, since it already 'the VM' for Ruby.
    &lt;/p&gt;
  &lt;dd&gt;
      
  &lt;/dd&gt;
&lt;/dd&gt;
&lt;dt&gt;
&lt;strong&gt;ko1&lt;/strong&gt;:&lt;/dt&gt;
  &lt;dd&gt;
    &lt;p&gt;
      YARV is vanished :)
    &lt;/p&gt;

    &lt;p&gt;
      In fact, I'm removing "yarv" words from structure names, function names,
      and file names.  YARV is only code name that not made by *Matz*.  Now,
      YARV is not "Yet Another".  In this article, I use "YARV" words as
      current Ruby trunk on official repository.
    &lt;/p&gt;

    &lt;p&gt;
      At first, YARV is simple stack machine which run pseudo sequential
      instructions.  Old interpreter (matzruby) *traverses* abstract syntax
      tree (AST) naively.  Obviously it's slow.  YARV compile that AST to YARV
      bytecode and run it.
    &lt;/p&gt;

    &lt;p&gt;
      Secondly, YARV uses native thread (that supported by OS or so) to
      implement Ruby thread.  It means that you can run *blocking* task in
      extension libraries.  &lt;em&gt;(On Ruby's spec, blocking task should be
      interrupted by &lt;code&gt;Thread#raise&lt;/code&gt;. To know details, see
      &lt;a href="http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/10252"&gt;[ruby-core:10252]&lt;/a&gt;.)&lt;/em&gt;
      Because thread creation is slower than matzruby (green thread), you
      shouldn't make many threads at a time. Supporting native thread *does not*
      means that you can run Ruby scripts in *parallel* on parallel machine such
      as Multi-Core CPUs.  Current implementation uses Giant VM Lock to avoid
      synchronization problems.  &lt;em&gt;(Many extension libraries doesn't care
      thread safety.  See &lt;code&gt;array.c&lt;/code&gt;, &lt;code&gt;string.c&lt;/code&gt;,
      etc.)&lt;/em&gt;
    &lt;/p&gt;

    &lt;p&gt;
      Thirdly, I made many optimization like specialized instructions, etc.
      These features are my purpose of developing YARV.  Toy benchmarks run
      fast because of these optimization techniques.
    &lt;/p&gt;

    &lt;p&gt;
      YARV doesn't change parser/syntax/specs (matz'
      &lt;span style="text-decoration: line-through"&gt;hobby&lt;/span&gt;task), GC
      (memory/object management), and extension libraries like
      &lt;code&gt;String&lt;/code&gt;/&lt;code&gt;Array&lt;/code&gt;/&lt;code&gt;Hash&lt;/code&gt;/&lt;code&gt;Regexp&lt;/code&gt;/etc.
      Therefore your script doesn't run fast on YARV if bottleneck is string
      processing, or so.
    &lt;/p&gt;
  &lt;dd&gt;
&lt;/dd&gt;
&lt;/dd&gt;
&lt;/dl&gt;&lt;p&gt;&lt;strong&gt;Congratulations to you both for completing Ruby/YARV merger recently. That must have been a lot of work, but I know it has the whole Ruby world very excited. Now that the merger has taken place, how do you see this changing the way Ruby is developed?&lt;/strong&gt;&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;
&lt;strong&gt;Matz&lt;/strong&gt;:&lt;/dt&gt;
  &lt;dd&gt;
    &lt;p&gt;
      Congrats should go to Koichi who has done a lot of work.  I am moving
      my developing from matzruby (a branch for my old interpreter) to
      trunk (the yarv).  Recently I have implemented some new features on
      the trunk, for example, class local instance variables and new local
      variable scope.  The transition will complete pretty soon.
    &lt;/p&gt;

    &lt;p&gt;
      Since the trunk is originally Koichi's work, I need more help from
      others especially from Koichi than before.  I know everything about
      the previous interpreter (well, most of them), but there are still
      mysteries in the new one.  I am well satisfied with new one.  It's
      clearer, well-formed, and faster.
    &lt;/p&gt;
  &lt;dd&gt;
      
  &lt;/dd&gt;
&lt;/dd&gt;
&lt;dt&gt;
&lt;strong&gt;ko1&lt;/strong&gt;:&lt;/dt&gt;
  &lt;dd&gt;
    &lt;p&gt;
      Thank you.  I'm a newbie of Ruby developer (in fact, I didn't have CVS
      account to commit any ruby codes).  So I can't say how change on ruby
      development :)
    &lt;/p&gt;
  &lt;dd&gt;
&lt;/dd&gt;
&lt;/dd&gt;
&lt;/dl&gt;&lt;p&gt;&lt;strong&gt;When will the first production release of Ruby running on YARV by available for all Rubyists to play with?&lt;/strong&gt;&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;
&lt;strong&gt;Matz&lt;/strong&gt;:&lt;/dt&gt;
  &lt;dd&gt;
    &lt;p&gt;
      Short answer: now.
    &lt;/p&gt;

    &lt;p&gt;
      Longer answer: the YARV is already publicly avaiblabe via our
      Subversion repository.  You can fetch and play with it now.  But the
      first public "release" from us will be Christmas 2007, if we are as
      diligent as we should be.  Knowing how lazy I am, I will try not to be
      a stumbling block for the release. ;-)
    &lt;/p&gt;
  &lt;dd&gt;
&lt;/dd&gt;
&lt;/dd&gt;
&lt;/dl&gt;</content>
    <author>
      <name>James Edward Gray II</name>
    </author>
  </entry>
  <entry>
    <title>The Ruby VM Serial Interview</title>
    <link rel="alternate" href="http://graysoftinc.com/the-ruby-vm-interview/the-ruby-vm-serial-interview"/>
    <id>tag:graysoftinc.com,2007-02-16:/posts/28</id>
    <updated>2014-04-04T21:40:57Z</updated>
    <summary>This article outlines a new series where we will see Matz and Koichi discussing the beating heart of our favorite programming language.</summary>
    <content type="html">&lt;p&gt;I have really enjoyed reading Pat Eyler's &lt;a href="http://on-ruby.blogspot.com/search/label/rubinius"&gt;Rubinius Serial Interview&lt;/a&gt; and Nick Sieger's spun-off &lt;a href="http://blog.nicksieger.com/articles/tag/jrubyserialinterview"&gt;JRuby Serial Interview&lt;/a&gt;.  It's very educational to read what the developers have to say about their projects and ideas.&lt;/p&gt;

&lt;p&gt;The more I read though, the more I wanted the equivalent content for the official Ruby VM.  I asked Matz and Koichi if they would be willing to answer questions from me and they agreed to do so.  We are now ready to share their responses with the community.&lt;/p&gt;

&lt;p&gt;This will be a &lt;em&gt;serial interview&lt;/em&gt; as Pat Eyler calls them.  We will deliver regular episodes until I run out of good questions or Matz and Koichi get sick of me bothering them, whichever comes first.  I will ask the questions in the interview, but feel free to make suggestions in the comments to this article.&lt;/p&gt;

&lt;p&gt;One last note:  we are not promising any kind of schedule for the episodes.  Matz and Koichi are heroically providing their answers in English.  We want to respect how much work that is and give them all the time they need to do that.  Personally, I cannot thank them enough.&lt;/p&gt;

&lt;p&gt;With that, I give you the episode index:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; In this &lt;a href="/the-ruby-vm-interview/the-ruby-vm-episode-i"&gt;first episode&lt;/a&gt; I ask Matz and Koichi to introduce themselves and their roles as well as to give us an update on where we are with the Ruby VM.&lt;/li&gt;
&lt;li&gt; In the &lt;a href="/the-ruby-vm-interview/the-ruby-vm-episode-ii"&gt;second episode&lt;/a&gt; I ask Matz and Koichi about for their thoughts on the alternate Ruby implementations and how they see them changing Ruby's development.&lt;/li&gt;
&lt;li&gt; In the &lt;a href="/the-ruby-vm-interview/the-ruby-vm-episode-iii"&gt;third episode&lt;/a&gt; Matz and Koichi discuss the past, present and future of Ruby threading.&lt;/li&gt;
&lt;li&gt; In the &lt;a href="/the-ruby-vm-interview/the-ruby-vm-episode-iv"&gt;fourth episode&lt;/a&gt; Matz tells us a little about how m17n is shaping up.&lt;/li&gt;
&lt;li&gt; In the &lt;a href="/the-ruby-vm-interview/the-ruby-vm-episode-v"&gt;fifth episode&lt;/a&gt; Koichi gives us the inside story on optimization in the new VM.&lt;/li&gt;
&lt;/ol&gt;</content>
    <author>
      <name>James Edward Gray II</name>
    </author>
  </entry>
  <entry>
    <title>news_to_mail.rb</title>
    <link rel="alternate" href="http://graysoftinc.com/the-gateway/news-to-mailrb"/>
    <id>tag:graysoftinc.com,2006-12-12:/posts/27</id>
    <updated>2014-04-04T19:16:17Z</updated>
    <summary>Here's the code for the other half of the Ruby Talk Gateway.  This code pulls posts from Usenet and emails them to the Ruby Talk mailing list.</summary>
    <content type="html">&lt;p&gt;&lt;em&gt;[&lt;strong&gt;Note&lt;/strong&gt;:  You need to know &lt;a href="/the-gateway/what-is-the-ruby-talk-gateway"&gt;what the Gateway is&lt;/a&gt; before reading this article.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The second half of the Ruby Gateway runs as a cron job every five minutes and is expected to move all new newsgroup messages from the NNTP host to the Ruby Talk mailing list.  The cron invocation is simply:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="sr"&gt;/path/&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;news_to_mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="sr"&gt;/path/&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;news_to_mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This side of the Gateway is not piped any messages and exit codes from it are not monitored.  It needs to tend its own affairs.&lt;/p&gt;

&lt;h4&gt;The Code&lt;/h4&gt;

&lt;p&gt;Here's the source:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;".."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;freeze&lt;/span&gt;
&lt;span class="no"&gt;DATA_DIR&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;freeze&lt;/span&gt;
&lt;span class="no"&gt;LAST_ID_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DATA_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"last_news_id.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;freeze&lt;/span&gt;
&lt;span class="no"&gt;PID_FILE&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DATA_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"pid.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;freeze&lt;/span&gt;

&lt;span class="vg"&gt;$LOAD_PATH&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lib"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The script begins by locating the root directory of the Gateway source and the data sub directory, building constants for two external files it will interact with, and adjusting the &lt;code&gt;$LOAD_PATH&lt;/code&gt; so that it can &lt;code&gt;require&lt;/code&gt; needed resources.&lt;/p&gt;

&lt;p&gt;Here are those &lt;code&gt;require&lt;/code&gt;s:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"servers_config"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"nntp"&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"net/smtp"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"logger"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"timeout"&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The first two requires are the same code used by &lt;a href="/the-gateway/mail-to-newsrb"&gt;the other half of the Gateway&lt;/a&gt;.  The last three are standard Ruby libraries the code will make use of.&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# prepare log&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ARGV&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shift&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="vg"&gt;$stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"%Y-%m-%d %H:%M "&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This code opens a log file to record its status and adjusts the date and time formatting.&lt;/p&gt;

&lt;p&gt;With setup complete, it's now vital to ensure that the current execution is the only copy of the code we are running.  Should the Gateway get behind and fail to complete before the cron job launches another copy, duplicate messages could be sent to the list or worse.&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# make sure only one copy is ever running at a time&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PID_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CREAT&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EXCL&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WRONLY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="vg"&gt;$$&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;at_exit&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PID_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt;
      &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Unable to unlink pid file:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
  &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PID_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_i&lt;/span&gt; &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warn&lt;/span&gt; &lt;span class="s2"&gt;"Process &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; was already running"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The script attempts to write its process ID number to a file.  The &lt;code&gt;File::CREAT|File:EXCL&lt;/code&gt; options ensure that the file is created or an exception is thrown, if it already existed.  When the exception comes it is assumed that an earlier execution is still running and the script exits.  If able to create the process ID file, the scripts knows it is the only copy running and it is safe to proceed.  The code then immediately arranges for Ruby to remove the file on exit.&lt;/p&gt;

&lt;p&gt;Note that to check for the file then create it if missing would introduce a race condition.  Another execution could create the file between check and creation.  As unlikely as that is for a five minute cron job, it's better to play things safe and just try the creation directly.&lt;/p&gt;

&lt;p&gt;Having made it this far, the script needs to refresh its memory of where it was in the list of newsgroup messages:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# find out what the last news item sent was&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="n"&gt;last_id_sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;LAST_ID_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"Unable to get message ID of last news post:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A simple text file is used to hold the message number of the last file seen.  The code reads that file back to know where to resume its work.&lt;/p&gt;

&lt;p&gt;It's now time to connect to Usenet.&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# &lt;/span&gt;
&lt;span class="c1"&gt;# connect to NNTP host, switch to newgroup, and see how many messages are&lt;/span&gt;
&lt;span class="c1"&gt;# available to read&lt;/span&gt;
&lt;span class="c1"&gt;# &lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_id_available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;nntp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NNTP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWS_SERVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NNTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NNTP_PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWS_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWS_PASS&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;last_id_available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWSGROUP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;to_i&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"The NNTP connection timed out."&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"Unable to establish connection to NNTP host:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This code tries to establish a connection to the NNTP host and switch to the comp.lang.ruby group being monitored.  A reasonable timeout is imposed on this code and will be for all network operations attempted.  If the connection is slow for some reason, another attempt can be made by the next cron execution.&lt;/p&gt;

&lt;p&gt;The script now advances the NNTP reader to the last message seen, which turns out to be complicated to get right:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# switch to the last message we sent (or the first real message before that)&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_id_sent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"The NNTP message shift timed out."&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Bad article number"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;last_id_sent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nonzero?&lt;/span&gt;
    &lt;span class="n"&gt;last_id_sent&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;retry&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"Unable to switch to the last message:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The problem here is that Usenet messages can be revoked.  When that happens, the last message the script saw may no longer exist.  The real goal is the message &lt;em&gt;after&lt;/em&gt; the last seen post though and an NNTP &lt;code&gt;NEXT&lt;/code&gt; command will skip missing IDs.  Given that, the code backs up in the count until it can find an existing message.  &lt;code&gt;NEXT&lt;/code&gt;ing forward from that will skip the vanished IDs and land the reader at the desired later message.&lt;/p&gt;

&lt;p&gt;To make that leap forward in the message count, the script enters an infinite loop of message processing:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# main event loop&lt;/span&gt;
&lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# advance to the next message, if there is one&lt;/span&gt;
  &lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;more_messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;new_last_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;to_i&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
      &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Advancing the current NNTP message timed out."&lt;/span&gt;
      &lt;span class="nb"&gt;exit&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;more_messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;more_messages&lt;/span&gt;

  &lt;span class="c1"&gt;# reality check that we are moving forward&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;new_last_id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;last_id_sent&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"new_last_id (&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) &amp;lt;= last_id_sent "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
              &lt;span class="s2"&gt;"(&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;last_id_sent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;), quitting to prevent sending duplicates"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As I said, a &lt;code&gt;NEXT&lt;/code&gt; command is sent to the host bringing the reader to an unseen message.  A quick sanity check is then made to ensure the count is rising.&lt;/p&gt;

&lt;p&gt;Note that this is also the code to determine when there are no more messages.  If the &lt;code&gt;NEXT&lt;/code&gt; command fails, the message processing loop terminates.  You really need to used this combination of an infinite loop and last message detection when working with Usenet.  Any method of counting is pointless, thanks to the numbering issues I described.&lt;/p&gt;

&lt;p&gt;At this point, it's time to actually start reading the post:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="c1"&gt;# pull the headers for the current messae&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
      &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Fetching the message headers timed out."&lt;/span&gt;
      &lt;span class="nb"&gt;exit&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Unable to retrieve headers for message #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
              &lt;span class="s2"&gt;"will try again later:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# &lt;/span&gt;
  &lt;span class="c1"&gt;# don't send articles that the mail_to_news program has previously forwarded&lt;/span&gt;
  &lt;span class="c1"&gt;# to the newsgroup (or we'd loop)&lt;/span&gt;
  &lt;span class="c1"&gt;# &lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^X-rubymirror:/&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Skipping message #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, sent by mail_to_news"&lt;/span&gt;
    &lt;span class="k"&gt;next&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This code pulls the message headers and performs the loop check to ensure that messages sent by &lt;code&gt;mail_to_news.rb&lt;/code&gt; are skipped.&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="c1"&gt;# pull the body for the current message&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
      &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Fetching the message body timed out."&lt;/span&gt;
      &lt;span class="nb"&gt;exit&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Unable to retrieve body for message #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
              &lt;span class="s2"&gt;"will try again later:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# scan headers for message data&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^Subject:\s+([^\r\n]*)/&lt;/span&gt;     &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"?"&lt;/span&gt;
  &lt;span class="n"&gt;from&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^From:\s+([^\r\n]*)/&lt;/span&gt;        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"?"&lt;/span&gt;

  &lt;span class="c1"&gt;# ensure the message is properly addressed&lt;/span&gt;
  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^To:/&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"To: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MAILING_LIST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Sending message #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;new_last_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -- &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The body is then pulled.  The script then scans for some message details in the headers to allow us to log accurately about the post.  The message is also addressed.&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="c1"&gt;# extracting path information (poster's host, first nntp hop)&lt;/span&gt;
  &lt;span class="n"&gt;posting_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^NNTP-Posting-Host:\s+(\S[^\r\n]*)/i&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;

  &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^Path:\s+([^\r\n]*)/i&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
  &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete_if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;hop&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;hop&lt;/span&gt; &lt;span class="o"&gt;!~&lt;/span&gt; &lt;span class="sr"&gt;/^(([^.]+)\.)+([^.]+)$/&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;first_hop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;

  &lt;span class="c1"&gt;# adding pseudo "Received:" header and rubymirror header&lt;/span&gt;
  &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;END_RECEIVED&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="sh"&gt;Received: from #{first_hop} (#{first_hop})&lt;/span&gt;
&lt;span class="sh"&gt;     by #{ServersConfig::NEWSGROUP} with NTTP id #{new_last_id}&lt;/span&gt;
&lt;span class="sh"&gt;     for &amp;lt;#{ServersConfig::MAILING_LIST}&amp;gt;; #{Time.now.to_s}&lt;/span&gt;
&lt;span class="sh"&gt;Received: from [#{posting_host}]&lt;/span&gt;
&lt;span class="sh"&gt;     by #{first_hop} (unknown) with NNTP id #{new_last_id}&lt;/span&gt;
&lt;span class="sh"&gt;     for &amp;lt;#{ServersConfig::NEWSGROUP}&amp;gt;; #{Time.now.to_s}&lt;/span&gt;
&lt;span class="sh"&gt;Received: from Usenet via a Usenet to mail gateway located at&lt;/span&gt;
&lt;span class="sh"&gt;     #{ServersConfig::NEWSGROUP}.  This service provided as a courtesy&lt;/span&gt;
&lt;span class="sh"&gt;     to the ruby-talk mailing list.  If this message is SPAM, its&lt;/span&gt;
&lt;span class="sh"&gt;     ultimate origin is Usenet, not this gateway program.  All&lt;/span&gt;
&lt;span class="sh"&gt;     subscribers to the ruby-talk mailing list agree to receive the&lt;/span&gt;
&lt;span class="sh"&gt;     Usenet postings made to comp.lang.ruby via this gateway.  Please&lt;/span&gt;
&lt;span class="sh"&gt;     see http://www.ruby-lang.org/ruby-talk-usenet-policy.html.&lt;/span&gt;
&lt;span class="sh"&gt;X-From-Usenet: see Received: header above.&lt;/span&gt;
&lt;span class="sh"&gt;X-rubymirror: yes&lt;/span&gt;

&lt;span class="no"&gt;END_RECEIVED&lt;/span&gt;

  &lt;span class="c1"&gt;# build final message&lt;/span&gt;
  &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Message looks like: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The above adds a header explaining the service and constructs the final message.&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="c1"&gt;# attempt to send email&lt;/span&gt;
  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vg"&gt;$DEBUG&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SMTP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SMTP_SERVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;smtp&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;smtp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MAIL_SENDER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MAILING_LIST&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"The SMTP connection timed out."&lt;/span&gt;
        &lt;span class="nb"&gt;exit&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt;
      &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"Unable to send email:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"...  Sent."&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here a connection is established to the SMTP server and the email is sent.  The &lt;code&gt;$DEBUG&lt;/code&gt; flag allows me to spot check Gateway functionality without actually sending emails to Ruby Talk.&lt;/p&gt;

&lt;p&gt;On to the final step:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="c1"&gt;# record new high-water mark&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vg"&gt;$DEBUG&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;new_last_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;last_id_sent&lt;/span&gt;
      &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;LAST_ID_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;new_last_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Unable to write message ID to file:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;last_id_sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_last_id&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"New last message sent ID:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;last_id_sent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Getting this far means the script sent an email successfully, so it records the new last seen ID for future runs.  That final &lt;code&gt;end&lt;/code&gt; signals the end of the message processing event loop.&lt;/p&gt;

&lt;h4&gt;Possible Improvements&lt;/h4&gt;

&lt;p&gt;This code is pretty feature complete.&lt;/p&gt;

&lt;p&gt;About the only enhancement I can think of would be to DRY up some of the timeout and error handling code.  That's trickier that you might guess because of the extra layers of scoping plus the error messages and exit codes.  We almost need a multi-block syntax here.  If someone poses a reasonable alternative I'll consider it, but I've tried to keep the code pretty straight forward and the truth is that is just may not be worth the effort.&lt;/p&gt;

&lt;p&gt;Any comments or suggestions for the Gateway can be left as comments below.&lt;/p&gt;</content>
    <author>
      <name>James Edward Gray II</name>
    </author>
  </entry>
  <entry>
    <title>mail_to_news.rb</title>
    <link rel="alternate" href="http://graysoftinc.com/the-gateway/mail-to-newsrb"/>
    <id>tag:graysoftinc.com,2006-12-05:/posts/26</id>
    <updated>2014-04-04T19:20:41Z</updated>
    <summary>Here's a breakdown for half of the Ruby Talk Gateway code.  This script pushes mail messages up to our Usenet host.</summary>
    <content type="html">&lt;p&gt;&lt;em&gt;[&lt;strong&gt;Note&lt;/strong&gt;:  You need to know &lt;a href="/the-gateway/what-is-the-ruby-talk-gateway"&gt;what the Gateway is&lt;/a&gt; before reading this article.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are two halves to the Ruby Gateway.  One half runs as a qmail filter for an email address on the Ruby Talk mailing list.  Every message sent to that address is piped through this filter with a shell script like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ruby /path/to/gateway/bin/mail_to_news.rb /path/to/mail_to_news.log
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The email is piped to the filter via the standard input and the code is expected to handle the message by posting it to comp.lang.ruby or choosing to ignore it.  If the filter exits normally, qmail considers the matter handled.  A non-zero exit code will cause the filter to be called with that same message again later.&lt;/p&gt;

&lt;h4&gt;The Code&lt;/h4&gt;

&lt;p&gt;Let's dive right into the source of this half of the Gateway:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;".."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;freeze&lt;/span&gt;

&lt;span class="vg"&gt;$LOAD_PATH&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;GATEWAY_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lib"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The code above just sets things up so this script can &lt;code&gt;require&lt;/code&gt; some other files in the project normally.  Here are those &lt;code&gt;require&lt;/code&gt;s:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"servers_config"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"nntp"&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"net/smtp"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"logger"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"timeout"&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The last three requires are standard Ruby libraries.  The first two are not.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;servers_config.rb&lt;/code&gt; file sets up a &lt;code&gt;ServerConfig&lt;/code&gt; &lt;code&gt;Module&lt;/code&gt; with information needed to connect to our email and Usenet hosts.  I will not show this file, but the references should be obvious when see them.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;nntp.rb&lt;/code&gt; is pretty much a vendored copy of the &lt;a href="http://rubygems.org/gems/ruby-net-nntp"&gt;net-nntp library&lt;/a&gt;.  I've made some minor changes for debugging output purposes, but the library functions the same.&lt;/p&gt;

&lt;p&gt;We're now ready to initiate logging.  Here's the code that starts that process:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# prepare log&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ARGV&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shift&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="vg"&gt;$stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"%Y-%m-%d %H:%M "&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That just builds a &lt;code&gt;Logger&lt;/code&gt; object and cleans up the default date and time formatting.&lt;/p&gt;

&lt;p&gt;Now it's time to start setting variables in preparation for the coming email parse:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# only allow certain headers through&lt;/span&gt;
&lt;span class="no"&gt;VALID_HEADERS&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[ From Subject References In-Reply-To Message-Id Content-Type&lt;/span&gt;
&lt;span class="sx"&gt;                       Content-Transfer-Encoding Date X-ML-Name X-Mail-Count&lt;/span&gt;
&lt;span class="sx"&gt;                       X-X-Sender ]&lt;/span&gt;
&lt;span class="n"&gt;valid_headers_re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^(?:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;VALID_HEADERS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"|"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;):/i&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Gateway passes through only a subset of the email headers for the Usenet post.  The above is the list of those headers and the &lt;code&gt;Regexp&lt;/code&gt; that will locate them.&lt;/p&gt;

&lt;p&gt;Now, there are two types of messages we do not wish to forward:  spam and a message sent to Ruby Talk by the other half of the Gateway (causing an infinite loop of sending).  The following code prepares flags for these conditions:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# message flags&lt;/span&gt;
&lt;span class="n"&gt;spam&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="n"&gt;mirrored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The following code allocates variables to hold key header information parsed from the message:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# header data&lt;/span&gt;
&lt;span class="n"&gt;msg_id&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
&lt;span class="n"&gt;subject&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
&lt;span class="n"&gt;from&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
&lt;span class="n"&gt;reply_to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
&lt;span class="n"&gt;ref&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;
&lt;span class="n"&gt;head&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;END_RECEIVED&lt;/span&gt;  &lt;span class="c1"&gt;# build received header, including loop flag&lt;/span&gt;
&lt;span class="sh"&gt;Newsgroups: #{ServersConfig::NEWSGROUP}&lt;/span&gt;
&lt;span class="sh"&gt;X-received-from: This message has been automatically forwarded from the&lt;/span&gt;
&lt;span class="sh"&gt;   ruby-talk mailing list by a gateway at #{ServersConfig::NEWSGROUP}. If it is&lt;/span&gt;
&lt;span class="sh"&gt;   SPAM, it did not originate at #{ServersConfig::NEWSGROUP}. Please report the&lt;/span&gt;
&lt;span class="sh"&gt;   original sender, and not us. Thanks!&lt;/span&gt;
&lt;span class="sh"&gt;   Please see http://hypermetrics.com/rubyhacker/clrFAQ.html#tag24 too.&lt;/span&gt;
&lt;span class="sh"&gt;X-rubymirror: yes&lt;/span&gt;
&lt;span class="no"&gt;END_RECEIVED&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that we get the headers started with an &lt;code&gt;X-received-from&lt;/code&gt; explaining our service and add the &lt;code&gt;X-rubymirror&lt;/code&gt; flag the other half of the Gateway will use to detect that this half of the Gateway sent this new post we are creating.&lt;/p&gt;

&lt;p&gt;Now we need to parse the email headers:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# process message headers&lt;/span&gt;
&lt;span class="n"&gt;valid_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="vg"&gt;$stdin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/^X-Spam-Status: Yes/&lt;/span&gt;      &lt;span class="c1"&gt;# flag message as spam&lt;/span&gt;
    &lt;span class="n"&gt;spam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/^X-rubymirror: yes/&lt;/span&gt;       &lt;span class="c1"&gt;# flag messages from news_to_mail&lt;/span&gt;
    &lt;span class="n"&gt;mirrored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/^\s*$/&lt;/span&gt;                    &lt;span class="c1"&gt;# end of headers&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/^\s/&lt;/span&gt;                      &lt;span class="c1"&gt;# continuation line&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;valid_header&lt;/span&gt;  &lt;span class="c1"&gt;# only allow after valid headers&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;valid_headers_re&lt;/span&gt;           &lt;span class="c1"&gt;# valid header&lt;/span&gt;
    &lt;span class="n"&gt;valid_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

    &lt;span class="c1"&gt;# parse header data&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/Message-Id:\s+(.*)/i&lt;/span&gt;
      &lt;span class="n"&gt;msg_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\.+&amp;gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/In-Reply-To:\s*(.*)/i&lt;/span&gt;
      &lt;span class="n"&gt;reply_to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/References:\s*(.*)/i&lt;/span&gt;
      &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/^Subject:\s*(.*)/i&lt;/span&gt;
      &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/^From:\s*(.*)/i&lt;/span&gt;
      &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;                            &lt;span class="c1"&gt;# invalid header, discard&lt;/span&gt;
    &lt;span class="n"&gt;valid_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There's nothing too tricky in the above code.  We match headers with simple expressions, pulling the information we need into variables.  We also set flags as appropriate and add to the headers we have started for the newsgroup post.  This code stops reading at the blank line signaling the end of the email headers.&lt;/p&gt;

&lt;p&gt;The code above didn't address flagged messages immediately, because we wanted to be able to log the key details about them.  We now have those details, so it's time to address the flags:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# skip any flagged messages&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mirrored&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Skipping message #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, sent by news_to_mail"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;spam&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Ignoring Spam #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -- &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see, flagged messages are noted in the log and we exit cleanly without further processing.&lt;/p&gt;

&lt;p&gt;The Gateway does some final header doctoring in an attempt to set a reasonable References header and also includes the Ruby Talk message id for reader reference:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# doctor headers for Ruby Talk&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nil?&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reply_to&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nil?&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^Re:/&lt;/span&gt;
            &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"In-Reply-To: &amp;lt;this_is_a_dummy_message-id@rubygate&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"References: &amp;lt;this_is_a_dummy_message-id@rubygate&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"References: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;reply_to&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"X-ruby-talk: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We are finally ready to construct a complete Usenet post:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# construct final message&lt;/span&gt;
&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$stdin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;
&lt;span class="n"&gt;msg&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gsub!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\r?\n/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Sending message #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -- &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Message looks like: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The above code just joins the headers and existing message body, cleans up the newlines and logs our progress.&lt;/p&gt;

&lt;p&gt;Actually sending the message is a two-step process.  First we connect to our Usenet host:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# connect to NNTP host&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="n"&gt;nntp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;nntp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NNTP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWS_SERVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NNTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NNTP_PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWS_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="no"&gt;ServersConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NEWS_PASS&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"The NNTP connection timed out."&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"Unable to establish connection to NNTP host:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Above you can see several references to the &lt;code&gt;ServerConfig&lt;/code&gt; &lt;code&gt;Module&lt;/code&gt; I spoke of earlier.  These constants contain exactly what their names indicate.&lt;/p&gt;

&lt;p&gt;Note that we exit with an error code if anything goes wrong here, assuming the problem is temporary and allowing qmail to try again later.&lt;/p&gt;

&lt;p&gt;The final step is to send the message:&lt;/p&gt;

&lt;div class="highlight highlight-ruby"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# attempt to send newsgroup post&lt;/span&gt;
&lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vg"&gt;$DEBUG&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nntp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
      &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"The NNTP post timed out."&lt;/span&gt;
      &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatal&lt;/span&gt; &lt;span class="s2"&gt;"Unable to post to NNTP host:  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"...  Sent.  nntp.post() result = &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The above code makes the post and logs what we have accomplished.  Again we exit with error codes if something goes wrong, to signal retries.  The &lt;code&gt;$DEBUG&lt;/code&gt; check allows me to test Gateway operation with everything but the actual post send when needed.&lt;/p&gt;

&lt;h4&gt;Possible Improvements&lt;/h4&gt;

&lt;p&gt;Usenet and email are two different worlds with opposing rules.  Our Usenet host, like many, does not allow the posting of multipart/alternative messages (used to send HTML email).  Some have expressed a desire for the Gateway to &lt;em&gt;convert&lt;/em&gt; these messages into a Usenet safe format.  This could possibly be done by using the text/plain variant of content, when provided, and stripping the HTML when it is not.  This change is of low importance to me, since I don't believe posters should be sending HTML email to Ruby Talk.&lt;/p&gt;

&lt;p&gt;In a similar vein, some Usenet hosts reject certain types of multipart/mixed messages (used to send email attachments), generally those that have Base 64 encoded portions to avoid allowing binary content through.  Our host allows such posts, but they may not be well circulated on Usenet for these reasons.  Again we might be able to inline the content for these files, but this could get pretty tricky for some attachments.  For example, imagine a post with a zip archive of files.  This problem interests me more than HTML email.&lt;/p&gt;

&lt;p&gt;The first step to either off these changes is probably to switch to a real email parsing library.  I imagine the original code didn't use one because the choices weren't convenient when the Gateway was designed.  I just cleaned up the code in my rewrite and don't have enough experience with such libraries to select the proper replacement.  Odds are this could simplify a fair portion of the Gateway code though, if we find the right one.  We are looking for a library that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Makes it easy to read email headers.&lt;/li&gt;
&lt;li&gt;Allow us to set new headers, for things like the no-mirror flag.&lt;/li&gt;
&lt;li&gt;Allows us to remove unwanted headers.  Alternately I guess we could build a new message object and copy over the headers we wish to keep.&lt;/li&gt;
&lt;li&gt;Supports easy manipulation of multipart/alternative and multipart/mixed content.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;If you have experience with such a library, please leave a comment below showing how this could be used to simplify the code above.&lt;/p&gt;</content>
    <author>
      <name>James Edward Gray II</name>
    </author>
  </entry>
  <entry>
    <title>Hacking the Gateway</title>
    <link rel="alternate" href="http://graysoftinc.com/the-gateway/hacking-the-gateway"/>
    <id>tag:graysoftinc.com,2006-12-04:/posts/25</id>
    <updated>2014-04-04T14:45:37Z</updated>
    <summary>Here are the rules for getting changes into the Ruby Talk Gateway code.</summary>
    <content type="html">&lt;p&gt;&lt;em&gt;[&lt;strong&gt;Update&lt;/strong&gt;:  The Ruby Gateway was retired in June of 2011.  Our community simply grew past the point were we needed to combine the various groups, in my opinion.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Though I rewrote the current Gateway and I handle the maintenance, it really belongs to the Ruby community.  Because of that, I'm going to release the two primary source files on this blog for all to view and critique.  This may have value to those who want to know how the Gateway works, those who would like to implement similar technologies, and those who would like to purpose changes to the Gateway code.&lt;/p&gt;

&lt;p&gt;I do welcome purposed changes to the Gateway, but let's set some ground rules for the right way to make suggestions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I will show the important elements of the Gateway code and do my best to explain it as I go.  In return, please take the time to read what I write about the code and try to understand how it works.  Poorly developed change requests increase my maintenance time with the Gateway, which all comes out of my free time, so please be considerate.&lt;/li&gt;
&lt;li&gt;You purpose changes to the Gateway by commenting on the code articles.  This is intended to be a public discussion with all of us working together.  Don't email me or Ruby Talk ideas, I'm monitoring the comments here.&lt;/li&gt;
&lt;li&gt;Show code in your requests.  I don't want to throw the Gateway in a publicly accessible Subversion repository and start taking patches for several reasons.  If you want a change, convince me to implement it.  The best way to do that is to throw around some code showing me how we would build your request and how it would make the Gateway better.&lt;/li&gt;
&lt;li&gt;I am thinking about some elements of the Gateway you are not, like the fact that I run this code on a server provided by my work where security is a consideration and the level of maintenance a change will inflict on me.  I ask only that you keep this in mind as we debate changes.  In return, I will be as open minded to improvements as possible.&lt;/li&gt;
&lt;li&gt;Gateway changes will not happen overnight.  (See note about free time above.)  Please be patient.&lt;/li&gt;
&lt;/ul&gt;</content>
    <author>
      <name>James Edward Gray II</name>
    </author>
  </entry>
  <entry>
    <title>What is the Ruby Talk Gateway?</title>
    <link rel="alternate" href="http://graysoftinc.com/the-gateway/what-is-the-ruby-talk-gateway"/>
    <id>tag:graysoftinc.com,2006-12-01:/posts/24</id>
    <updated>2014-04-04T01:49:45Z</updated>
    <summary>The article describes and old, but still in common use, feature of the Ruby community:  The Ruby Talk/comp.lang.ruby Gateway.</summary>
    <content type="html">&lt;p&gt;&lt;em&gt;[&lt;strong&gt;Update&lt;/strong&gt;:  The Ruby Gateway was retired in June of 2011.  Our community simply grew past the point were we needed to combine the various groups, in my opinion.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Ruby community makes use of both email and Usenet communication, in addition to other resources.  The primary mailing list is &lt;a href="http://www.ruby-lang.org/en/community/mailing-lists/"&gt;Ruby Talk&lt;/a&gt; and the primary Usenet group is &lt;a href="http://groups-beta.google.com/group/comp.lang.ruby/topics"&gt;comp.lang.ruby&lt;/a&gt;.  These two services are joined by the Ruby Gateway.&lt;/p&gt;

&lt;p&gt;In 2001 &lt;a href="http://www.pragmaticprogrammer.com/"&gt;The Pragmatic Programmers&lt;/a&gt; wrote the initial version of the Ruby Gateway to ferry messages back and forth between these two resources.  Emails sent to Ruby Talk are posted as Usenet messages and Usenet posts are forwarded to Ruby Talk by the Gateway.  The Gateway has had a few guardians and code changes since then, but the functionality remains the same.&lt;/p&gt;

&lt;p&gt;I'm am the current caretaker of the Ruby Gateway.  Highgroove Studios generously provides hosting for it and I monitor the system for problems.  I also wrote the current version of the Gateway.&lt;/p&gt;

&lt;p&gt;You are free to &lt;a href="mailto:james@graysoftinc.com?subject=Ruby%20Gateway%20Issue"&gt;report Gateway problems&lt;/a&gt; for me to look into.  Before you do though, please read the following notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; I rewrote the entire Gateway after I assumed control of it.  My code was deployed on December 4th, 2006, so anything before that is history.  If you raise issues, please make sure they involve posts after that date.&lt;/li&gt;
&lt;li&gt;I now have very detailed logs on everything the Gateway does, so please be specific.  For example, please send me links to exact messages that appeared on one side of the Gateway, but not the other.&lt;/li&gt;
&lt;li&gt;Our Usenet host does not allow us to post HTML emails (multipart/alternative).  This is not changing.  These messages are not supported.  (Yes, that means you should &lt;strong&gt;not&lt;/strong&gt; be sending HTML email to Ruby Talk.)&lt;/li&gt;
&lt;li&gt;Our Gateway is an NNTP ↔ email Gateway.  There has been at least one instance of a Usenet post using ancient header formatting predating NNTP.  These messages are not supported.&lt;/li&gt;
&lt;/ul&gt;</content>
    <author>
      <name>James Edward Gray II</name>
    </author>
  </entry>
</feed>
