secretary 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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