secretary 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,25 @@
1
+
2
+ === 0.2.0 / 2008-08-29
3
+
4
+ * 1 minor enhancements
5
+
6
+ * Added more complete RDocs for the Secretary class. RDocs for gophers
7
+ are planned in the future.
8
+
9
+ * 1 major change
10
+
11
+ * Fundamentally changed how gophers validate data. Now there is a general
12
+ Gopher#validate(data) method, which checks if the data's type matches
13
+ the gopher's data-type, and also checks if the data fulfills the
14
+ new Gopher#validation_proc attribute, which can be passed through the
15
+ initializer's block.
16
+
17
+ === 0.1.0 / 2008-08-12
18
+
19
+ * 1 minor enhancement
20
+
21
+ * An RSpec file that runs all specs, all_spec.rb, has been added
22
+
1
23
  === 0.0.1 / 2008-08-11
2
24
 
3
25
  * 1 major enhancement
@@ -7,9 +7,11 @@ lib/secretary/error.rb
7
7
  lib/secretary/gopher.rb
8
8
  lib/secretary/gopher/yaml.rb
9
9
  spec/spec_helper.rb
10
- spec/secretary_spec.rb
10
+ spec/secretary.rb
11
11
  spec/error_spec.rb
12
- spec/gopher_spec.rb
12
+ spec/gopher/yaml_spec.rb
13
13
  spec/hash.yaml
14
14
  spec/array.yaml
15
- spec/broken-yaml.txt
15
+ spec/broken-yaml.yaml
16
+ spec/one-row.csv
17
+ spec/two-rows.csv
data/README.txt CHANGED
@@ -4,35 +4,25 @@
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
- Secretary is a gem who makes managing simple preference files in Ruby easy
8
- and painless. If you tire of being bound to PStore or YAML and want a
9
- consistent, pluggable way to read and write preferences, your Secretary would
10
- be happy to help!
7
+ Secretary is a gem who makes managing simple preference files in Ruby easier
8
+ and more painless.
11
9
 
12
10
  == FEATURES:
13
11
 
14
- * She's smooth and simple: just import and initialize!
15
- * She flexible: though she uses the simple YAML markup language by default, she
16
- can easily switch to any other format!
17
- * She supports custom defaults, a big essential!
12
+ * She encapsulates preference files in a single, easily manageable object
13
+ * She's smooth and simple: just import and initialize
18
14
  * She's easily extensible--subclass Secretary::Gopher to be able to load any
19
- kind of file you wish!
20
- * All in all, she's a small, no-frills object who gives you one less thing to
21
- worry about. It can't be any easier!
15
+ kind of file you wish
22
16
 
23
17
  == SYNOPSIS:
24
18
 
25
- import 'secretary/simple'
19
+ import 'secretary'
26
20
 
27
- preferences = {'name': 'Samantha', 'port': 151}
28
- defaults = {'name': '', 'port': 80}
29
-
30
- secretary = Secretary.new 'preferences.yaml', defaults
21
+ secretary = Secretary.new 'preferences.yaml', :defaults => {'name': '', 'port': 80}
31
22
 
32
23
  # The first argument is the path to the file that she will sync to.
33
- # The second argument indicates the object that contains her defaults.
34
- # There's also an optional third argument that determines how she will sync
35
- # with her file--by default, it'll be YAML.
24
+ # The second argument is a option hash.
25
+ # Passing in a :defaults option gives the Hash that contains her defaults.
36
26
 
37
27
  secretary['url'] = 'http://samantha.name'
38
28
 
@@ -47,8 +37,7 @@ be happy to help!
47
37
  * At least Ruby 1.8.0
48
38
  * The standard forwarable.rb
49
39
  * The standard yaml.rb
50
- * At least RSpec 1.1 (but only if you want to unit test using Secretary's
51
- included specs)
40
+ * At least RSpec 1.1 (but only if you want to use the included specs)
52
41
 
53
42
  == INSTALL:
54
43
 
@@ -59,34 +48,9 @@ install it automatically:
59
48
 
60
49
  == NOTES:
61
50
 
62
- I'm planning to add support for managing arbitrary objects, not just Hashes,
63
- as well as support for more formats like CSV (easy), JSON (easier), and XML
64
- (hard).
65
-
66
- In addition, this is my first programming project ever, and I'm sort of not
67
- used to this. I'd be very happy to hear your comments and bugs on Rubyforge.
68
-
69
- == LICENSE:
70
-
71
- (The MIT License)
72
-
73
- Copyright (c) 2008
74
-
75
- Permission is hereby granted, free of charge, to any person obtaining
76
- a copy of this software and associated documentation files (the
77
- 'Software'), to deal in the Software without restriction, including
78
- without limitation the rights to use, copy, modify, merge, publish,
79
- distribute, sublicense, and/or sell copies of the Software, and to
80
- permit persons to whom the Software is furnished to do so, subject to
81
- the following conditions:
51
+ Throughout this library's documentation, Secretaries will be referred to as
52
+ "she"s, and Gophers will be referred to as "he"s.
82
53
 
83
- The above copyright notice and this permission notice shall be
84
- included in all copies or substantial portions of the Software.
54
+ I'm planning to add support for for more formats such as JSON and (gulp) XML.
85
55
 
86
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
87
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
88
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
89
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
90
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
91
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
92
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
56
+ I'd be very happy to hear your comments and bugs on Rubyforge.
@@ -1,48 +1,101 @@
1
1
  $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
2
+
3
+ class Secretary
4
+ end
5
+
2
6
  require 'secretary/error'
3
7
  require 'secretary/gopher/yaml'
4
8
  require 'forwardable'
5
9
 
10
+ # A Secretary easily manages a data structure (by default a Hash only) and a
11
+ # corresponding data file (by default a YAML file). To this end it utilizes a
12
+ # gopher (by default a Secretary::Gopher::YAML object). The Secretary tells
13
+ # the gopher to fetch or save certain files whenever it's needed, but the
14
+ # Secretary herself manages the file path, any default options, and the data
15
+ # itself.
16
+
17
+ # If you subclass the Secretary class to be able to store a different kind of
18
+ # data, like, say, an Array instead of a Hash, then you'll need to override the
19
+ # contents and to_hash methods. (In addition, you may want to use a different
20
+ # kind of gopher too. See the documentation on Secretary::Gopher for info about
21
+ # that.)
22
+
6
23
  class Secretary
7
- VERSION = '0.0.1'
24
+ VERSION = '0.1.0'
8
25
  extend Forwardable
9
26
  include Enumerable
10
- attr_reader :file_path, :defaults, :gopher
27
+ attr_accessor :file_path, :defaults
28
+ attr_reader :gopher
11
29
 
12
- def initialize(file_path, defaults={}, gopher_class=Gopher::YAML)
30
+ # Creates a brand-spanking new Secretary who takes charge of the file at the
31
+ # given file_path.
32
+ # You can give an options hash. Two options are eaten by the Secretary
33
+ # intializer (the rest are passed into the gopher initializer). They are
34
+ # :defaults, which is a Hash containing the default data, and :gopher, which
35
+ # is a Gopher class from which the Secretary's gopher will be created.
36
+ # She will raise an ArgumentError if you give a gopher class object that does
37
+ # not respond to :new, or if the newly initialized gopher object cannot act
38
+ # like a Gopher (i.e. if it cannot respond to :load and :save).
39
+ # If she finds a file at the file_path you give her, then she'll combine the
40
+ # data that her new gopher loads from the file with the defaults that you
41
+ # gave her to get her contents.
42
+ # If no file is found at the given file_path, then just the defaults are
43
+ # put into the contents.
44
+ def initialize(file_path, options={})
45
+ defaults = options.delete(:defaults) || {}
46
+ gopher_class = options.delete(:gopher) || Gopher::YAML
13
47
  unless gopher_class.respond_to? :new
14
48
  raise ArgumentError, "given gopher class is not a class (#{gopher_class})"
15
49
  end
16
50
  @file_path = file_path
17
51
  @defaults = defaults
18
- @gopher = gopher_class.new Hash
19
- unless @gopher.respond_to? :load_from_path
20
- raise ArgumentError, "given gopher class\'s members do not respond to :load_from_path (#{gopher})"
52
+ @gopher = gopher_class.new options
53
+ unless @gopher.respond_to? :load
54
+ raise ArgumentError, "given gopher class\'s members do not respond to " +
55
+ ":load (#{gopher})"
56
+ end
57
+ unless @gopher.respond_to? :save
58
+ raise ArgumentError, "given gopher class\'s members do not respond to " +
59
+ ":save (#{gopher})"
21
60
  end
22
61
  @contents = {}
23
- reload
62
+ reload!
24
63
  end
25
64
 
26
- def_delegators :to_hash, :[], :include?, :each, :each_pair, :each_key, :each_value
27
- def_delegators :@contents, :[]=, :delete
65
+ def_delegators :to_hash, :[], :include?, :each, :each_pair,
66
+ :each_key, :each_value
67
+ def_delegators :@contents, :[]=, :delete, :update
28
68
 
69
+ # A Secretary is equal to another object if the other object responds to
70
+ # :file_path, :defaults, :gopher, and :contents, and if the objects' values of
71
+ # those corresponding methods are equal to each other.
29
72
  def ==(other)
30
73
  other.respond_to? :file_path and other.respond_to? :defaults \
31
- and other.respond_to? :gopher and other.respond_to? :to_hash \
74
+ and other.respond_to? :gopher and other.respond_to? :contents \
32
75
  and file_path == other.file_path and defaults == other.defaults \
33
- and gopher == other.gopher and to_hash == other.to_hash
76
+ and gopher == other.gopher and contents == other.contents
34
77
  end
35
78
 
36
- def to_hash
79
+ # Returns a new object containing the Secretary's data that she originally got
80
+ # from her file. It consists of the her defaults overridden by her file's
81
+ # data. Note that this method returns a new object, which means that it is
82
+ # only meant for inspection; modifying the object it returns will not affect
83
+ # her data. If you want to modify her data, directly tell her to use the []=
84
+ # or delete methods.
85
+ def contents
37
86
  defaults.merge @contents
38
87
  end
39
88
 
40
- def reload
89
+ def to_hash
90
+ contents
91
+ end
92
+
93
+ # Reloads data from the Secretary's file. If she succeeds, then this method
94
+ # returns true. If she can't find a file at her file_path, then this method
95
+ # returns false.
96
+ def reload!
41
97
  begin
42
- loaded_data = gopher.load_from_path(file_path)
43
- unless loaded_data.respond_to? :to_hash
44
- raise IOError, "gopher's loaded data is unconvertable to a Hash and thus invalid (#{loaded_data})"
45
- end
98
+ loaded_data = gopher.load(file_path)
46
99
  @contents.merge! loaded_data
47
100
  return true
48
101
  rescue Error::MissingFile
@@ -50,6 +103,7 @@ class Secretary
50
103
  end
51
104
  end
52
105
 
106
+ # Saves its contents to its file path.
53
107
  def save
54
108
  gopher.save self.to_hash, file_path
55
109
  end
@@ -1,31 +1,31 @@
1
+
1
2
  class Secretary
2
-
3
- module Error
4
-
5
- class MissingFile < IOError
3
+ end
6
4
 
7
- def initialize(missing_file_path)
8
- super "no such file or directory (#{missing_file_path})"
9
- end
5
+ module Secretary::Error
6
+
7
+ class MissingFile < IOError
10
8
 
9
+ def initialize(missing_file_path)
10
+ super "no such file or directory (#{missing_file_path})"
11
11
  end
12
12
 
13
- class Parsing < IOError
13
+ end
14
14
 
15
- def initialize(parsing_error, file_type)
16
- super "attempted to parse an invalid #{file_type} file (#{parsing_error})"
17
- end
15
+ class Parsing < IOError
18
16
 
17
+ def initialize(parsing_error, file_type)
18
+ super "attempted to parse an invalid #{file_type} file (#{parsing_error})"
19
19
  end
20
20
 
21
- class InvalidData < IOError
21
+ end
22
22
 
23
- def initialize(data, valid_data_type)
24
- super "preference file of invalid data type (should be #{valid_data_type} instead of #{data.class})"
25
- end
23
+ class InvalidData < IOError
26
24
 
25
+ def initialize(data)
26
+ super "preference file contains invalid data (#{data.inspect})"
27
27
  end
28
28
 
29
29
  end
30
-
31
- end
30
+
31
+ end
@@ -1,59 +1,95 @@
1
1
  require 'secretary/error'
2
2
 
3
3
  class Secretary
4
-
5
- class Gopher
6
- # Abstract base class that requires methods #parse_file(input_file),
7
- # #valid_file_type(), and #emit(data) to survive.
8
- VERSION = '0.0.1'
9
- attr_reader :valid_data_type
10
- class << self
11
- attr_reader :valid_file_type
12
- end
13
-
14
- def initialize(valid_data_type)
15
- @valid_data_type = valid_data_type
16
- end
4
+ end
17
5
 
18
- def load_from_path(file_path)
19
- if File.exist? file_path
20
- open file_path do |file|
21
- return load_from_file(file)
22
- end
23
- else
24
- raise Secretary::Error::MissingFile.new(file_path)
25
- end
26
- end
6
+ # Requires one of the following:
7
+ # - parse_file
8
+ # - load_from_file
9
+ #
10
+ # As well as one of the following:
11
+ # - emit
12
+ # - save_into_file_path
13
+ #
14
+ # You may override the following:
15
+ # - validate
16
+ # - raw
17
+ # - cook
18
+ # - parse_file
19
+ # - load_from_file
20
+ # - emit
21
+ # - save_into_file_path
27
22
 
28
- def load_from_file(file)
29
- begin
30
- file_data = parse_file file
31
- rescue Secretary::Error::Parsing => parsing_error
32
- raise parsing_error
33
- end
34
- unless file_data.kind_of? valid_data_type
35
- raise Secretary::Error::InvalidData.new(file_data, valid_data_type)
36
- end
37
- return file_data
38
- end
39
-
40
- def save(data, file_path)
41
- unless data.kind_of? valid_data_type
42
- raise ArgumentError, "invalid data #{data} is not a kind of #{valid_data_type}"
43
- end
44
- open file_path, 'w' do |file|
45
- file.puts emit(data)
46
- end
23
+ class Secretary::Gopher
24
+ attr_reader :options
25
+ class << self
26
+ attr_reader :file_type
27
+ @file_type = 'data'
28
+ end
29
+
30
+ def initialize options={}
31
+ @options = options
32
+ end
33
+
34
+ def file_type
35
+ self.class.file_type
36
+ end
37
+
38
+ def == other
39
+ self.class == other.class
40
+ end
41
+
42
+ def load file_path
43
+ if File.exist? file_path
44
+ file_data = load_from_file_path file_path
45
+ else
46
+ raise Secretary::Error::MissingFile.new(file_path)
47
47
  end
48
-
49
- def valid_file_type
50
- self.class.valid_file_type
48
+ unless validate file_data
49
+ raise Secretary::Error::InvalidData.new(file_data)
51
50
  end
52
-
53
- def ==(other)
54
- self.class == other.class
51
+ return cook(file_data)
52
+ end
53
+
54
+ def save(data, file_path)
55
+ unless validate data then raise ArgumentError,
56
+ "invalid data #{data} is not a kind of #{data_type}"
57
+ end
58
+ save_into_file_path raw(data), file_path
59
+ end
60
+
61
+ def validate data
62
+ true
63
+ end
64
+
65
+ def raw data
66
+ data
67
+ end
68
+
69
+ def cook data
70
+ data
71
+ end
72
+
73
+ protected
74
+
75
+ def load_from_file_path(file_path)
76
+ open file_path do |file|
77
+ return load_from_file(file)
78
+ end
79
+ end
80
+
81
+ def load_from_file(file)
82
+ begin
83
+ parse_file file
84
+ rescue Secretary::Error::Parsing
85
+ raise
55
86
  end
56
-
57
87
  end
58
88
 
89
+ def save_into_file_path(data, file_path)
90
+ open file_path, 'w' do |file|
91
+ file.puts emit(data)
92
+ end
93
+ end
94
+
59
95
  end