Module: Oughtve

Defined in:
lib/oughtve/tangent.rb,
lib/oughtve.rb,
lib/oughtve/verse.rb,
lib/oughtve/chapter.rb,
lib/oughtve/options.rb,
lib/oughtve/database.rb

Overview

LICENCE

Authors

 See doc/AUTHORS.

Copyright

 Copyright (c) 2006-2010 Eero Saynatkari, all rights reserved.

Licence

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:

 - Redistributions of source code must retain the above copyright
   notice, this list of conditions, the following disclaimer and
   attribution to the original authors.

 - Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions, the following disclaimer and
   attribution to the original authors in the documentation and/or
   other materials provided with the distribution.

 - The names of the authors may not be used to endorse or promote
   products derived from this software without specific prior
   written permission.

Disclaimer

This software is provided "as is" and without any express or
implied warranties, including, without limitation, the implied
warranties of merchantability and fitness for a particular purpose.
Authors are not responsible for any damages, direct or indirect.

Defined Under Namespace

Modules: Database Classes: Chapter, Tangent, Verse

Constant Summary collapse

ResourceDirectory =

Directory in which our resources are stored.

File.expand_path File.join(ENV["HOME"], ".oughtve")
TimeFormat =

The standard time format

"%Y-%m-%d %H:%M:%S"

Class Method Summary collapse

Class Method Details

.bootstrapObject

Bootstrap a brand new setup.



76
77
78
79
80
81
# File 'lib/oughtve.rb', line 76

def self.bootstrap(*)
  FileUtils.mkdir_p ResourceDirectory and Database.bootstrap
  run %w[ --new --tangent default --directory / ]

  "Oughtve has been set up. A default tangent has been created."
end

.chapter(parameters) ⇒ Object

Mark old chapter and start new one.



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/oughtve/tangent.rb', line 111

def self.chapter(parameters)
  tangent = tangent_for parameters
  previous = tangent.current_chapter

  previous.summary = parameters.endnote
  previous.summary << " " << parameters.rest.join(" ") unless parameters.rest.empty?

  previous.ended = parameters.time
  previous.save

  chapter = tangent.chapters.new
  tangent.current_chapter = chapter

  tangent.save
  chapter.save

  "Chapter of \"#{tangent.name}\" finished."
end

.delete(parameters) ⇒ Object

TODO:

Does not touch Chapters or Verses.

Delete a tangent.

Raises:

  • (ArgumentError)


136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/oughtve/tangent.rb', line 136

def self.delete(parameters)
  raise ArgumentError, "Must provide name to delete!" unless parameters.name
  raise ArgumentError, "Default tangent may not be deleted!" if parameters.name == "default"

  tangent = tangent_for parameters
  path = tangent.dir

  raise ArgumentError, "No tangent named `#{parameters.name}'!" unless tangent

  tangent.destroy

  "Deleted `#{parameters.name}', which was bound at #{path}"
end

.listObject

Show all open defined Tangents and their base directories.



154
155
156
157
158
# File 'lib/oughtve/tangent.rb', line 154

def self.list(*)
  " Defined tangents:\n" <<
  "===================\n" <<
  Tangent.all.map {|t| " * #{t.name} (#{t.dir})" }.join("\n")
end

.parse_from(arguments) ⇒ Object

Parse allowed options from given arguments



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/oughtve/options.rb', line 55

def self.parse_from(arguments)
  result = OpenStruct.new

  # Grab time here to be a bit closer to true
  result.time = Time.now

  def result.action!(given)
    raise ArgumentError, "Two actions given: 1) --#{action}, 2) --#{given}" if action
    self.action = given
  end

  OptionParser.new do |opts|

    opts.banner = "Usage: #{$0} [--ACTION [OPTIONS]]"

    opts.separator ""
    opts.separator "  Actions:"


    # Actions

    opts.on "-s", "--scribe [TEXT]", "Enter a new note." do |text|
      result.action! :scribe
      result.text = text
    end

    opts.on "-S", "--strike [ID_OR_REGEXP]", "Strike out a note." do |id_or_regexp|
      result.action! :strike

      if id_or_regexp
        begin
          result.serial = Integer(id_or_regexp)
        rescue ArgumentError
          result.regexp = /#{Regexp.escape id_or_regexp}/
        end
      end
    end

    opts.on "-w", "--show [all | old]", "Show notes for tangent(s). [All notes / old chapters too]" do |specifier|
      result.action! :show

      if specifier
        case specifier
        when "all"
          result.all = true
        when "old"
          result.all = true
          result.old = true
        else
          raise ArgumentError, "--show #{specifier} is not a valid option!" 
        end
      end
    end

    opts.on "-l", "--list", "Show defined Tangents and their base directories." do
      result.action! :list
    end

    opts.on "-c", "--chapter ENDNOTE", "Mark end of chapter and start new one." do |endnote|
      result.action! :chapter
      result.endnote = endnote
    end


    # Options
    opts.separator ""
    opts.separator "  Maintenance Actions:"

    opts.on "-b", "--bootstrap", "Set up database and initial structures." do
      result.action! :bootstrap
    end

    opts.on "-n", "--new", "Create new Tangent. (Use -t to give it a name.)" do
      result.action! :tangent
    end

    opts.on "-D", "--delete [NAME]", "Delete tangent." do |name|
      result.action! :delete
      result.name = name
    end


    # Options
    opts.separator ""
    opts.separator "  Options:"

    opts.on "-d", "--directory DIR", "Use given directory instead of Dir.pwd." do |dir|
      result.dir = dir
    end

    opts.on "-i", "--id ID", "Use specific note ID." do |id|
      result.serial = id
    end

    opts.on "-m", "--match REGEXP", "Match note using regexp, specific branch only." do |regexp|
      result.regexp = /#{Regexp.escape regexp}/
    end

    opts.on "-t", "--tangent NAME", "Use named Tangent specifically." do |name|
      result.name = name
    end

    opts.on "-x", "--text TEXT", "Text to use for note." do |text|
      result.text = text
    end

    opts.on "-v", "--verbose", "Give extra information for some actions." do
      result.verbose = true
    end

    opts.on "-J", "--json", "JSON output" do
      result.format = :json
    end

    opts.on "-Y", "--yaml", "YAML output" do
      result.format = :yaml
    end


    # Bookkeeping stuff

    opts.on_tail "-h", "--help", "Display this message." do
      puts opts
      exit!
    end

  end.parse! arguments

rescue OptionParser::InvalidOption => e
  $stderr.print "\n#{e.message}\n\n"
  parse_from %w[ -h ]

else
  result.rest = arguments
  result.action = :scribe unless result.action
  result
end

.run(arguments) ⇒ Object

Entry point to the library.

Parses arguments and performs the requested action.



67
68
69
70
71
# File 'lib/oughtve.rb', line 67

def self.run(arguments)
  options = parse_from arguments

  Database.connect and self.send options.action, options
end

.scribe(parameters) ⇒ Object

Enter a new Verse.

Text may be given with or without –text option, or read from $stdin if no other text is found.

Raises:

  • (ArgumentError)


167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/oughtve/tangent.rb', line 167

def self.scribe(parameters)
  tangent = tangent_for parameters

  text = parameters.text || ""
  text << " " << parameters.rest.join(" ") unless parameters.rest.empty?

  if text.empty?
    $stdout.puts "Reading for `#{tangent.name}' from standard input, terminate with ^D."
    text = $stdin.read
  end

  raise ArgumentError, "No note!" if text.empty?

  tangent.current_chapter.verses.new(:text => text.strip, :time => parameters.time).save

  "So noted in \"#{tangent.name}\"."
end

.show(parameters) ⇒ Object

Output notes for tangent.

Optionally both open and closed; optionally old chapters too.



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/oughtve/tangent.rb', line 191

def self.show(parameters)
  tangent = tangent_for parameters

  case parameters.format
  when :yaml
    require "yaml"
    return tangent.to_hash.to_yaml
  when :json
    require "json"
    return tangent.to_hash.to_json
  end

  message = " #{tangent.name}\n"
  message << ("=" * (tangent.name.length + 2)) << "\n"

  chapters = if parameters.old
               tangent.chapters.sort_by {|chapter| chapter.ended || Time.now }.reverse
             else
               [tangent.current_chapter]
             end

  chapters.each {|chapter|
    if chapter.summary
      summary = "#{chapter.summary}, closed #{chapter.ended.strftime TimeFormat}"
      message << "\n\n\n" << summary << "\n" << ("=" * (summary.length + 2)) << "\n"
    end

    closed, open = chapter.verses.partition {|v| v.struck }

    # @todo Remove duplication here, unless output stays different..
    message << "\n Open:\n-------\n"
    message <<  open.reverse.map {|v|
                  if parameters.verbose
                    " * #{v.text} (##{v.id} #{v.time.strftime TimeFormat})"
                  else
                    " * #{v.text}"
                  end
                }.join("\n")

    if parameters.all
      message << "\n\n Closed:\n---------\n"
      message <<  closed.reverse.map {|v|
                    if parameters.verbose
                      " * #{v.text} (##{v.id} #{v.time.strftime TimeFormat})"
                    else
                      " * #{v.text}"
                    end
                  }.join("\n")
    end
  }

  message
end

.strike(parameters) ⇒ Object

Strike out an existing note.

Used to mark a note completed (or just get rid of it.)

Raises:

  • (ArgumentError)


251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/oughtve/tangent.rb', line 251

def self.strike(parameters)
  verse = if parameters.serial
            Verse.get! parameters.serial
          else
            tangent = tangent_for parameters
            candidates =  tangent.current_chapter.verses.select {|v|
                            parameters.regexp =~ v.text
                          }

            if candidates.size < 1
              raise ArgumentError, "No match for #{parameters.regexp.inspect}!"
            elsif candidates.size > 1
              # TODO: Show matches
              raise ArgumentError, "Ambiguous #{parameters.regexp.inspect}!"
            end

            candidates.first
          end

  raise ArgumentError, "Already struck out: #{parameters.serial}" if verse.struck

  verse.struck = parameters.time
  verse.save

  "\"#{verse.text[0..20]}...\" has been stricken."
end

.tangent(parameters) ⇒ Object

Create a new Tangent.

Raises:

  • (ArgumentError)


282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/oughtve/tangent.rb', line 282

def self.tangent(parameters)
  dir = File.expand_path(parameters.dir) rescue Dir.pwd

  tangent = tangent_for parameters, :check_path_too

  if tangent
    if tangent.name == parameters.name
      raise ArgumentError, "Tangent `#{tangent.name}' already exists at #{tangent.dir}"
    end

    # Relationship with existing tangent?
    case dir <=> tangent.dir
    when -1
      # @todo Is this a necessary warning? Verbose only maybe? --rue
      warn "One or more existing tangents live below the new one (at least #{tangent.dir})"
    when 0
      raise ArgumentError, "#{tangent.dir} is already bound to `#{tangent.name}'!"
    else
      # Does not matter if it is a subdirectory
    end
  end

  tangent = Tangent.new

  tangent.dir = File.expand_path(parameters.dir) rescue Dir.pwd
  raise ArgumentError, "No such directory #{tangent.dir}" unless File.directory? tangent.dir

  tangent.name = parameters.name || tangent.dir

  chapter = tangent.chapters.new
  tangent.current_chapter = chapter

  tangent.save
  chapter.save

  "Created #{tangent.name} at #{tangent.dir}."
end