sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/
commit c578ee67b08e490f4c314a90aee4401c32b74ae9
parent d16bebcf34102579a935f890eb0a5adef372373d
Author: Whyme Lyu <callme5long@gmail.com>
Date:   Thu, 30 May 2013 06:00:14 -0700

Merge pull request #75 from 5long/migrate-to-psych

Migrate to Psych
Diffstat:
M .travis.yml | 2 +-
A bin/sup-psych-ify-config-files | 16 ++++++++++++++++
M lib/sup.rb | 31 ++++++++++++++++---------------
M lib/sup/draft.rb | 7 +++++--
M lib/sup/modes/inbox_mode.rb | 2 +-
M lib/sup/source.rb | 4 ++--
M sup.gemspec | 9 ++++++++-
A test/test_yaml_migration.rb | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 129 insertions(+), 22 deletions(-)
diff --git a/.travis.yml b/.travis.yml
@@ -3,7 +3,7 @@ language: ruby
 rvm:
   - 2.0.0
   - 1.9.3
-  - 1.8.7
+  - 1.9.2
 
 before_install:
   - sudo apt-get update -qq
diff --git a/bin/sup-psych-ify-config-files b/bin/sup-psych-ify-config-files
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
+require "sup"
+require "fileutils"
+
+Redwood.start
+
+fn = Redwood::SOURCE_FN
+FileUtils.cp fn, "#{fn}.syck_bak"
+
+Redwood::SourceManager.load_sources fn
+Redwood::SourceManager.save_sources fn, true
+
+Redwood.finish
diff --git a/lib/sup.rb b/lib/sup.rb
@@ -1,11 +1,6 @@
 require 'rubygems'
-
-require 'syck'
 require 'yaml'
-if YAML.const_defined? :ENGINE
-  YAML::ENGINE.yamler = 'syck'
-end
-
+YAML::ENGINE.yamler = 'psych'
 require 'zlib'
 require 'thread'
 require 'fileutils'
@@ -28,18 +23,23 @@ end
 class Module
   def yaml_properties *props
     props = props.map { |p| p.to_s }
-    vars = props.map { |p| "@#{p}" }
-    klass = self
-    path = klass.name.gsub(/::/, "/")
 
-    klass.instance_eval do
-      define_method(:to_yaml_properties) { vars }
-      define_method(:to_yaml_type) { "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}" }
+    path = name.gsub(/::/, "/")
+    yaml_tag "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}"
+
+    define_method :init_with do |coder|
+      initialize(*coder.map.values_at(*props))
     end
 
-    YAML.add_domain_type("#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}", path) do |type, val|
-      klass.new(*props.map { |p| val[p] })
+    define_method :encode_with do |coder|
+      coder.map = props.inject({}) do |hash, key|
+        hash[key] = instance_variable_get("@#{key}")
+        hash
+      end
     end
+
+    # Legacy
+    Psych.load_tags["!#{Redwood::LEGACY_YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}"] = self
   end
 end
 
@@ -58,7 +58,8 @@ module Redwood
   SEARCH_FN  = File.join(BASE_DIR, "searches.txt")
   LOG_FN     = File.join(BASE_DIR, "log")
 
-  YAML_DOMAIN = "masanjin.net"
+  YAML_DOMAIN = "supmua.org"
+  LEGACY_YAML_DOMAIN = "masanjin.net"
   YAML_DATE = "2006-10-01"
 
   ## record exceptions thrown in threads nicely
diff --git a/lib/sup/draft.rb b/lib/sup/draft.rb
@@ -32,14 +32,17 @@ class DraftLoader < Source
   attr_accessor :dir
   yaml_properties
 
-  def initialize
-    dir = Redwood::DRAFT_DIR
+  def initialize dir=Redwood::DRAFT_DIR
     Dir.mkdir dir unless File.exists? dir
     super DraftManager.source_name, true, false
     @dir = dir
     @cur_offset = 0
   end
 
+  def properly_initialized?
+    !!(@dir && @cur_offset)
+  end
+
   def id; DraftManager.source_id; end
   def to_s; DraftManager.source_name; end
   def uri; DraftManager.source_name; end
diff --git a/lib/sup/modes/inbox_mode.rb b/lib/sup/modes/inbox_mode.rb
@@ -1,4 +1,4 @@
-require 'sup'
+require "sup/modes/thread_index_mode"
 
 module Redwood
 
diff --git a/lib/sup/source.rb b/lib/sup/source.rb
@@ -209,9 +209,9 @@ class SourceManager
     end
   end
 
-  def save_sources fn=Redwood::SOURCE_FN
+  def save_sources fn=Redwood::SOURCE_FN, force=false
     @source_mutex.synchronize do
-      if @sources_dirty
+      if @sources_dirty || force
         Redwood::save_yaml_obj sources, fn, false, true
       end
       @sources_dirty = false
diff --git a/sup.gemspec b/sup.gemspec
@@ -5,7 +5,8 @@ require 'sup/version'
 
 # Files
 SUP_EXECUTABLES = %w(sup sup-add sup-config sup-dump sup-import-dump
-  sup-recover-sources sup-sync sup-sync-back sup-tweak-labels)
+  sup-recover-sources sup-sync sup-sync-back sup-tweak-labels
+  sup-psych-ify-config-files)
 SUP_EXTRA_FILES = %w(CONTRIBUTORS README.md LICENSE History.txt ReleaseNotes)
 SUP_FILES =
   SUP_EXTRA_FILES +
@@ -33,9 +34,15 @@ module Redwood
       * Automatically tracking recent contacts
 DESC
     s.license = 'GPL-2'
+    # TODO: might want to add index migrating script here, too
+    s.post_install_message = <<-EOF
+SUP: Please run `sup-psych-ify-config-files` to migrate from 0.13 to 0.14
+    EOF
     s.files = SUP_FILES
     s.executables = SUP_EXECUTABLES
 
+    s.required_ruby_version = '>= 1.9.2'
+
     s.add_dependency "xapian-full-alaveteli", "~> 1.2"
     s.add_dependency "ncursesw-sup", "~> 1.3", ">= 1.3.1"
     s.add_dependency "rmail", ">= 0.17"
diff --git a/test/test_yaml_migration.rb b/test/test_yaml_migration.rb
@@ -0,0 +1,80 @@
+require "test_helper"
+
+require "sup"
+require "psych"
+
+describe "Sup's YAML util" do
+  describe "Module#yaml_properties" do
+    def build_class_with_name name, &b
+      Class.new do
+        meta_cls = class << self; self; end
+        meta_cls.send(:define_method, :name) { name }
+        class_exec(&b) unless b.nil?
+      end
+    end
+
+    after do
+      Psych.load_tags = {}
+      Psych.dump_tags = {}
+    end
+
+    it "defines YAML tag for class" do
+      cls = build_class_with_name 'Cls' do
+        yaml_properties
+      end
+
+      expected_yaml_tag = "!supmua.org,2006-10-01/Cls"
+
+      Psych.load_tags[expected_yaml_tag].must_equal cls
+      Psych.dump_tags[cls].must_equal expected_yaml_tag
+
+    end
+
+    it "Loads legacy YAML format as well" do
+      cls = build_class_with_name 'Cls' do
+        yaml_properties :id
+        attr_accessor :id
+        def initialize id
+          @id = id
+        end
+      end
+
+      Psych.load_tags["!masanjin.net,2006-10-01/Cls"].must_equal cls
+
+      yaml = <<EOF
+--- !masanjin.net,2006-10-01/Cls
+id: ID
+EOF
+      loaded = YAML.load(yaml)
+
+      loaded.id.must_equal 'ID'
+      loaded.must_be_kind_of cls
+    end
+
+    it "Dumps & loads w/ state re-initialized" do
+      cls = build_class_with_name 'Cls' do
+        yaml_properties :id
+        attr_accessor :id
+        attr_reader :flag
+
+        def initialize id
+          @id = id
+          @flag = true
+        end
+      end
+
+      instance = cls.new 'ID'
+
+      dumped = YAML.dump(instance)
+      loaded = YAML.load(dumped)
+
+      dumped.must_equal <<-EOF
+--- !supmua.org,2006-10-01/Cls
+id: ID
+      EOF
+
+      loaded.id.must_equal 'ID'
+      assert loaded.flag
+    end
+  end
+end