├── TODO ├── Rakefile ├── remparser.gemspec ├── README.md ├── bin └── rem2yaml.rb ├── test └── spec_remparser.rb └── lib └── remparser.treetop /TODO: -------------------------------------------------------------------------------- 1 | TODO 2 | ==== 3 | 4 | - Add support for remind -p 5 | - check bodytime against duration 6 | - Allow parsing more than one line 7 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'yard' 2 | require 'rake/clean' 3 | 4 | CLEAN.include('doc/', '*.gem') 5 | 6 | YARD::Rake::YardocTask.new do |t| 7 | t.files = ['bin/rem2yaml.rb', 'lib/remparser.treetop'] 8 | t.options = ['--main', 'README', '--markup', 'markdown'] 9 | end 10 | -------------------------------------------------------------------------------- /remparser.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | Gem::Specification.new do |s| 4 | s.name = %q{remparser} 5 | s.version = "0.0.2" 6 | 7 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 8 | s.authors = ["Patrick Hof"] 9 | s.date = %q{2011-04-11} 10 | s.email = %q{courts@offensivethinking.org} 11 | s.files = ["bin/rem2yaml.rb", "lib/remparser.treetop"] 12 | s.executables = ["rem2yaml.rb"] 13 | s.homepage = %q{http://www.offensivethinking.org} 14 | s.require_paths = ["lib"] 15 | s.rubygems_version = %q{1.3.6} 16 | s.summary = %q{A parser for 'remind -s' output} 17 | s.add_dependency('treetop', '>= 1.4.9') 18 | 19 | if s.respond_to? :specification_version then 20 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 21 | s.specification_version = 3 22 | 23 | if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then 24 | else 25 | end 26 | else 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | remparser 2 | ========= 3 | 4 | Author: Patrick Hof 5 | License: [CC0 1.0 Universal License](http://creativecommons.org/publicdomain/zero/1.0/legalcode) 6 | 7 | Download: git clone http://github.com/courts/remparser.git 8 | 9 | remparser is a simple PEG parser written in 10 | [Treetop](http://treetop.rubyforge.org/) to parse the output of ['remind 11 | -s'](http://www.roaringpenguin.com/products/remind) into a data structure. The 12 | simple command line program rem2yaml.rb is provided to show its usage. rem2yaml 13 | parses remind's output given on STDIN in a YAML data structure. 14 | 15 | rem2yaml.rb Command Line Usage 16 | --------------------------- 17 | 18 | Usage: rem2yaml.rb [options] < $STDIN 19 | 20 | Options: 21 | -------- 22 | -h Show this help 23 | 24 | Where $STDIN is the output of 'remind -s' 25 | 26 | 27 | Examples 28 | -------- 29 | 30 | remind -s12 ~/.reminders 01 Jan 2011 | rem2yaml.rb 31 | 32 | RubyGems 33 | -------- 34 | 35 | A gemspec file is included, so you can build and install remparser as a gem with: 36 | 37 | gem build remparser.gemspec 38 | gem install remparser-x.x.x.gem 39 | -------------------------------------------------------------------------------- /bin/rem2yaml.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'treetop' 4 | require 'polyglot' 5 | require 'yaml' 6 | require_relative File.join('..', 'lib', 'remparser') 7 | 8 | # A simple demonstration script converting the output of 'remind -s' to YAML, 9 | # showing the capabilities of the 'remparser' treetop parser. 10 | # 11 | # This software is licensed under the Creative 12 | # Commons CC0 1.0 Universal License. 13 | # To view a copy of this license, visit 14 | # http://creativecommons.org/publicdomain/zero/1.0/legalcode 15 | # 16 | # @author Patrick Hof 17 | 18 | if ARGV[0] == '-h' 19 | puts "Usage: #{__FILE__} [options] < $STDIN" 20 | puts "" 21 | puts "Options:" 22 | puts "--------" 23 | puts "-h Show this help" 24 | puts "" 25 | puts "Where $STDIN is the output of 'remind -s'" 26 | puts "" 27 | puts "Example:" 28 | puts "--------" 29 | puts "remind -s12 ~/.reminders 01 Jan 2011 | #{__FILE__}" 30 | exit 0 31 | end 32 | 33 | parser = RemindParser.new 34 | 35 | all_entries = [] 36 | $stdin.each_line do |line| 37 | res = parser.parse(line) 38 | if !res 39 | puts parser.failure_reason 40 | exit 1 41 | else 42 | all_entries << res.content_hash 43 | end 44 | end 45 | 46 | puts all_entries.to_yaml 47 | -------------------------------------------------------------------------------- /test/spec_remparser.rb: -------------------------------------------------------------------------------- 1 | require 'bacon' 2 | require 'treetop' 3 | require 'polyglot' 4 | require 'date' 5 | 6 | require_relative File.join('..', 'lib', 'remparser') 7 | 8 | describe RemindParser do 9 | 10 | before do 11 | @parser = RemindParser.new 12 | end 13 | 14 | it "should return a hash structure when asking for #content_hash" do 15 | line = "2011/04/07 * * * * Test Text" 16 | res = @parser.parse(line) 17 | 18 | res.content_hash.should.equal({ 19 | :date => Date.parse('2011/04/07'), 20 | :special => "*", 21 | :tag => "*", 22 | :dur => 0, 23 | :time => 0, 24 | :body => 25 | { 26 | :text => "Test Text" 27 | } 28 | }) 29 | end 30 | 31 | it "should parse the time given in the body if present" do 32 | line = "2011/04/08 * * 180 1200 8:00-11:00am Test Text" 33 | res = @parser.parse(line) 34 | 35 | res.content_hash.should.equal({ 36 | :date => Date.parse('2011/04/08'), 37 | :special => "*", 38 | :tag => "*", 39 | :dur => 180, 40 | :time => 1200, 41 | :body => { 42 | :text => "Test Text", 43 | :time => { 44 | :start => "8:00", 45 | :end => "11:00" 46 | } 47 | } 48 | }) 49 | end 50 | 51 | it "should parse the time given in the body if present, converting from 12h to 24h format" do 52 | line = "2011/04/08 * * 600 480 8:00am-6:00pm Test Text" 53 | res = @parser.parse(line) 54 | 55 | res.content_hash.should.equal({ 56 | :date => Date.parse('2011/04/08'), 57 | :special => "*", 58 | :tag => "*", 59 | :dur => 600, 60 | :time => 480, 61 | :body => { 62 | :text => "Test Text", 63 | :time => { 64 | :start => "8:00", 65 | :end => "18:00" 66 | } 67 | } 68 | }) 69 | end 70 | 71 | end 72 | -------------------------------------------------------------------------------- /lib/remparser.treetop: -------------------------------------------------------------------------------- 1 | require "date" 2 | 3 | # Simple parser for the output of 'remind -s'. 4 | # 5 | # This software is licensed under the Creative 6 | # Commons CC0 1.0 Universal License. 7 | # To view a copy of this license, visit 8 | # http://creativecommons.org/publicdomain/zero/1.0/legalcode 9 | # 10 | # @author Patrick Hof 11 | 12 | grammar Remind 13 | 14 | rule remline 15 | date space special space tag space dur space time space body { 16 | def content 17 | elements.map {|e| e.content}.reject {|e| e =~ /\s/} 18 | end 19 | def content_hash 20 | a = elements.map {|e| e.content}.reject {|e| e =~ /\s/} 21 | h = {} 22 | a.each do |e| 23 | h[e[0]] = e[1] 24 | end 25 | h 26 | end 27 | } 28 | end 29 | 30 | rule date 31 | [0-9] 4..4 '/' [0-9] 1..2 '/' [0-9] 1..2 { 32 | def content 33 | [:date, Date.parse(text_value)] 34 | end 35 | } 36 | end 37 | 38 | rule special 39 | ('*' / [a-zA-Z]+) { 40 | def content 41 | [:special, text_value] 42 | end 43 | } 44 | end 45 | 46 | rule tag 47 | ('*' / [a-zA-Z,]+) { 48 | def content 49 | [:tag, text_value] 50 | end 51 | } 52 | end 53 | 54 | rule dur 55 | ('*' / [0-9]+) { 56 | def content 57 | [:dur, text_value.to_i] 58 | end 59 | } 60 | end 61 | 62 | rule time 63 | ('*' / [0-9]+) { 64 | def content 65 | [:time, text_value.to_i] 66 | end 67 | } 68 | end 69 | 70 | rule body 71 | bodytime 0..1 space 0..1 string { 72 | def content 73 | btime = {} 74 | btime[:text] = elements[2].content.chomp 75 | if elements[0].elements.length > 0 76 | btime[:time] = elements[0].elements[0].content 77 | end 78 | [:body, btime] 79 | end 80 | } 81 | end 82 | 83 | rule bodytime 84 | hours tod 0..1 '-' hours tod 0..1 { 85 | def content 86 | hh,mm = elements[0].text_value.split(":") 87 | el = elements[1] 88 | hh = (hh.to_i % 12) + (el.text_value == 'am' || el.text_value == "" ? 0 : 12) 89 | starttime = "#{hh}:#{mm}" 90 | index = elements.length == 5 ? 3 : 2 91 | hh,mm = elements[index].text_value.split(":") 92 | hh = (hh.to_i % 12) + (elements[index + 1].text_value == 'am' ? 0 : 12) 93 | endtime = "#{hh}:#{mm}" 94 | {:start => starttime, 95 | :end => endtime} 96 | end 97 | } 98 | end 99 | 100 | rule hours 101 | [0-9] 1..2 ':' [0-9] 2..2 102 | end 103 | 104 | rule tod 105 | 'am' / 'pm' 106 | end 107 | 108 | rule space 109 | [ \t]* { 110 | def content 111 | text_value 112 | end 113 | } 114 | end 115 | 116 | rule string 117 | .* { 118 | def content 119 | text_value 120 | end 121 | } 122 | end 123 | 124 | end 125 | --------------------------------------------------------------------------------