imw 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.
Files changed (111) hide show
  1. data/.gitignore +15 -0
  2. data/CHANGELOG +0 -0
  3. data/LICENSE +674 -0
  4. data/README.rdoc +101 -0
  5. data/Rakefile +20 -0
  6. data/VERSION +1 -0
  7. data/etc/imwrc.rb +76 -0
  8. data/lib/imw.rb +42 -0
  9. data/lib/imw/boot.rb +58 -0
  10. data/lib/imw/dataset.rb +233 -0
  11. data/lib/imw/dataset/datamapper.rb +66 -0
  12. data/lib/imw/dataset/datamapper/time_and_user_stamps.rb +37 -0
  13. data/lib/imw/dataset/loaddump.rb +50 -0
  14. data/lib/imw/dataset/old/file_collection.rb +88 -0
  15. data/lib/imw/dataset/old/file_collection_utils.rb +71 -0
  16. data/lib/imw/dataset/scaffold.rb +132 -0
  17. data/lib/imw/dataset/scraped_uri.rb +305 -0
  18. data/lib/imw/dataset/scrub/old_working_scrubber.rb +87 -0
  19. data/lib/imw/dataset/scrub/scrub.rb +147 -0
  20. data/lib/imw/dataset/scrub/scrub_simple_url.rb +38 -0
  21. data/lib/imw/dataset/scrub/scrub_test.rb +60 -0
  22. data/lib/imw/dataset/scrub/slug.rb +101 -0
  23. data/lib/imw/dataset/stats.rb +73 -0
  24. data/lib/imw/dataset/stats/counter.rb +23 -0
  25. data/lib/imw/dataset/task.rb +38 -0
  26. data/lib/imw/dataset/workflow.rb +81 -0
  27. data/lib/imw/files.rb +110 -0
  28. data/lib/imw/files/archive.rb +113 -0
  29. data/lib/imw/files/basicfile.rb +122 -0
  30. data/lib/imw/files/binary.rb +28 -0
  31. data/lib/imw/files/compressed_file.rb +93 -0
  32. data/lib/imw/files/compressed_files_and_archives.rb +348 -0
  33. data/lib/imw/files/compressible.rb +103 -0
  34. data/lib/imw/files/csv.rb +112 -0
  35. data/lib/imw/files/json.rb +41 -0
  36. data/lib/imw/files/sgml.rb +65 -0
  37. data/lib/imw/files/text.rb +68 -0
  38. data/lib/imw/files/yaml.rb +46 -0
  39. data/lib/imw/packagers.rb +8 -0
  40. data/lib/imw/packagers/archiver.rb +108 -0
  41. data/lib/imw/packagers/s3_mover.rb +28 -0
  42. data/lib/imw/parsers.rb +7 -0
  43. data/lib/imw/parsers/html_parser.rb +382 -0
  44. data/lib/imw/parsers/html_parser/matchers.rb +306 -0
  45. data/lib/imw/parsers/line_parser.rb +87 -0
  46. data/lib/imw/parsers/regexp_parser.rb +72 -0
  47. data/lib/imw/utils.rb +24 -0
  48. data/lib/imw/utils/components.rb +61 -0
  49. data/lib/imw/utils/config.rb +46 -0
  50. data/lib/imw/utils/error.rb +54 -0
  51. data/lib/imw/utils/extensions/array.rb +125 -0
  52. data/lib/imw/utils/extensions/class/attribute_accessors.rb +8 -0
  53. data/lib/imw/utils/extensions/core.rb +43 -0
  54. data/lib/imw/utils/extensions/dir.rb +24 -0
  55. data/lib/imw/utils/extensions/file_core.rb +64 -0
  56. data/lib/imw/utils/extensions/hash.rb +218 -0
  57. data/lib/imw/utils/extensions/hpricot.rb +48 -0
  58. data/lib/imw/utils/extensions/string.rb +49 -0
  59. data/lib/imw/utils/extensions/struct.rb +42 -0
  60. data/lib/imw/utils/extensions/symbol.rb +28 -0
  61. data/lib/imw/utils/extensions/typed_struct.rb +22 -0
  62. data/lib/imw/utils/extensions/uri.rb +59 -0
  63. data/lib/imw/utils/log.rb +67 -0
  64. data/lib/imw/utils/misc.rb +63 -0
  65. data/lib/imw/utils/paths.rb +115 -0
  66. data/lib/imw/utils/uri.rb +59 -0
  67. data/lib/imw/utils/uuid.rb +33 -0
  68. data/lib/imw/utils/validate.rb +38 -0
  69. data/lib/imw/utils/version.rb +12 -0
  70. data/lib/imw/utils/view.rb +113 -0
  71. data/lib/imw/utils/view/dump_csv.rb +112 -0
  72. data/lib/imw/utils/view/dump_csv_older.rb +117 -0
  73. data/spec/data/sample.csv +131 -0
  74. data/spec/data/sample.tsv +131 -0
  75. data/spec/data/sample.txt +131 -0
  76. data/spec/data/sample.xml +653 -0
  77. data/spec/data/sample.yaml +652 -0
  78. data/spec/imw/dataset/datamapper/uri_spec.rb +43 -0
  79. data/spec/imw/dataset/datamapper_spec_helper.rb +11 -0
  80. data/spec/imw/files/archive_spec.rb +118 -0
  81. data/spec/imw/files/basicfile_spec.rb +121 -0
  82. data/spec/imw/files/bz2_spec.rb +32 -0
  83. data/spec/imw/files/compressed_file_spec.rb +96 -0
  84. data/spec/imw/files/compressible_spec.rb +100 -0
  85. data/spec/imw/files/file_spec.rb +144 -0
  86. data/spec/imw/files/gz_spec.rb +32 -0
  87. data/spec/imw/files/rar_spec.rb +33 -0
  88. data/spec/imw/files/tar_spec.rb +31 -0
  89. data/spec/imw/files/text_spec.rb +23 -0
  90. data/spec/imw/files/zip_spec.rb +31 -0
  91. data/spec/imw/files_spec.rb +38 -0
  92. data/spec/imw/packagers/archiver_spec.rb +125 -0
  93. data/spec/imw/packagers/s3_mover_spec.rb +7 -0
  94. data/spec/imw/parsers/line_parser_spec.rb +96 -0
  95. data/spec/imw/parsers/regexp_parser_spec.rb +42 -0
  96. data/spec/imw/utils/extensions/file_core_spec.rb +72 -0
  97. data/spec/imw/utils/extensions/find_spec.rb +113 -0
  98. data/spec/imw/utils/paths_spec.rb +38 -0
  99. data/spec/imw/workflow/rip/local_spec.rb +89 -0
  100. data/spec/imw/workflow/rip_spec.rb +27 -0
  101. data/spec/rcov.opts +1 -0
  102. data/spec/spec.opts +4 -0
  103. data/spec/spec_helper.rb +32 -0
  104. data/spec/support/archive_contents_matcher.rb +94 -0
  105. data/spec/support/custom_matchers.rb +21 -0
  106. data/spec/support/directory_contents_matcher.rb +61 -0
  107. data/spec/support/extensions.rb +18 -0
  108. data/spec/support/file_contents_matcher.rb +50 -0
  109. data/spec/support/random.rb +210 -0
  110. data/spec/support/without_regard_to_order_matcher.rb +58 -0
  111. metadata +196 -0
@@ -0,0 +1,24 @@
1
+ #
2
+ # h2. lib/imw/utils.rb -- utility functions
3
+ #
4
+ # == About
5
+ #
6
+ # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
7
+ # Copyright:: Copyright (c) 2008 infochimps.org
8
+ # License:: GPL 3.0
9
+ # Website:: http://infinitemonkeywrench.org/
10
+ #
11
+
12
+ require 'rubygems'
13
+ require 'imw/utils/error'
14
+ require 'imw/utils/log'
15
+ require 'imw/utils/config'
16
+ require 'imw/utils/paths'
17
+ require 'imw/utils/misc'
18
+ require 'imw/utils/components'
19
+ require 'imw/utils/extensions/core'
20
+ require 'fileutils'
21
+ require 'pathname'
22
+
23
+
24
+ # puts "#{File.basename(__FILE__)}: Early economists thought they would measure the utility of an action in units of `utils'. Really." # at bottom
@@ -0,0 +1,61 @@
1
+ #
2
+ # h2. lib/imw/utils/components.rb -- define separate components of IMW
3
+ #
4
+ # == About
5
+ #
6
+ # Defines a hash <tt>IMW::COMPONENTS</tt> which keys component names
7
+ # to the files to be required to implement each component and defines
8
+ # methods to load these files.
9
+ #
10
+ # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
11
+ # Copyright:: Copyright (c) 2008 infochimps.org
12
+ # License:: GPL 3.0
13
+ # Website:: http://infinitemonkeywrench.org/
14
+ #
15
+ # puts "#{File.basename(__FILE__)}: Something clever" # at bottom
16
+
17
+ require 'imw/utils/error'
18
+
19
+ module IMW
20
+
21
+ # Defines IMW components and the files required by each. Components
22
+ # can be accessed using <tt>IMW.load_components</tt> or
23
+ # <tt>IMW#imw_components</tt>.
24
+ COMPONENTS = {
25
+ :datamapper => ["imw/dataset/datamapper","imw/dataset/datamapper/time_and_user_stamps"],
26
+ :data_mapper => :datamapper,
27
+ :html_parser => "imw/parsers/html_parser",
28
+ :flat_file_parser => "imw/parsers/flat_file_parser",
29
+ :line_parser => "imw/parsers/line_parser",
30
+ :infochimps => ["imw/infochimps/infochimps_resource","imw/infochimps/icss"]
31
+ }
32
+
33
+ # Load components of IMW as needed,
34
+ #
35
+ # IMW.load_components :datamapper, :flat_file_parser
36
+ def self.load_components *args
37
+ args.each do |component_name|
38
+ begin
39
+ require component_name.to_s
40
+ rescue LoadError
41
+ component = IMW::COMPONENTS[component_name]
42
+ raise IMW::Error.new("#{component_name} is an invalid IMW component. See IMW::COMPONENTS.") unless component
43
+ if component.is_a? Array then
44
+ IMW.load_components *component
45
+ else
46
+ IMW.load_components component
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ # Load components of IMW as needed,
53
+ #
54
+ # include IMW
55
+ # imw_components :datamapper, :flat_file_parser
56
+ def imw_components *args
57
+ IMW.load_components *args
58
+ end
59
+
60
+ end
61
+
@@ -0,0 +1,46 @@
1
+ #
2
+ # h2. lib/imw/utils/config.rb -- configuration parsing
3
+ #
4
+ # == About
5
+ #
6
+ # IMW looks for configuration settings in the following places, in
7
+ # order of increasing precedence:
8
+ #
9
+ # 1. Settings defined directly in this file.
10
+ #
11
+ # 2. From the <tt>etc/imwrc</tt> file in the IMW root directory.
12
+ #
13
+ # 3. From the <tt>.imwrc</tt> file in the user's home directory (the
14
+ # filename can be changed; see
15
+ # <tt>IMW::Config::USER_CONFIG_FILE_BASENAME</tt>).
16
+ #
17
+ # 4. From the file defined by the environment variable +IMWRC+ (the
18
+ # value can be changed; see
19
+ # <tt>IMW::Config::USER_CONFIG_FILE_ENV_VARIABLE</tt>
20
+ #
21
+ # Settings not found in one configuration location will be searched
22
+ # for in locations of lesser precedence.
23
+ #
24
+ # *Note:* configuration files are plain Ruby code that will be directly
25
+ # evaluated.
26
+ #
27
+ # Relevant settings include
28
+ #
29
+ # * interfaces with external programs (+tar+, +wget+, &c.)
30
+ # * paths to directories where IMW reads/writes files
31
+ # * correspondences between file extensions and IMW file classes
32
+ #
33
+ # For more detailed information, see the default configuration file,
34
+ # <tt>etc/imwrc</tt>.
35
+ #
36
+ #
37
+ # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
38
+ # Copyright:: Copyright (c) 2008 infochimps.org
39
+ # License:: GPL 3.0
40
+ # Website:: http://infinitemonkeywrench.org/
41
+ #
42
+
43
+ module IMW
44
+ end
45
+
46
+ # puts "#{File.basename(__FILE__)}: You carefully adjust the settings on your Monkeywrench. Glob-monsters: beware!!" # at bottom
@@ -0,0 +1,54 @@
1
+ #
2
+ # h2. lib/imw/utils/error -- errors
3
+ #
4
+ # == About
5
+ #
6
+ # Error objects for IMW.
7
+ #
8
+ # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
9
+ # Copyright:: Copyright (c) 2008 infochimps.org
10
+ # License:: GPL 3.0
11
+ # Website:: http://infinitemonkeywrench.org/
12
+ #
13
+
14
+ module IMW
15
+
16
+ # A generic error class.
17
+ class Error < StandardError
18
+ end
19
+
20
+ class TypeError < TypeError
21
+ end
22
+
23
+ class ArgumentError < ArgumentError
24
+ end
25
+
26
+ class NotImplementedError < NotImplementedError
27
+ end
28
+
29
+ class ParseError < Error
30
+ end
31
+
32
+ # An error meant to be used when a system call goes awry. It will
33
+ # report exit status and the process id of the offending call.
34
+ class SystemCallError < IMW::Error
35
+
36
+ def initialize(message)
37
+ @message = message
38
+ end
39
+
40
+ def display
41
+ "(error code: #{$?.exitstatus}, pid: #{$?.pid}) #{@message}"
42
+ end
43
+
44
+ def to_s
45
+ "(error code: #{$?.exitstatus}, pid: #{$?.pid}) #{@message}"
46
+ end
47
+
48
+ end
49
+
50
+ # A error for improperly specified, inappropriate, or broken paths.
51
+ class PathError < IMW::Error
52
+ end
53
+
54
+ end
@@ -0,0 +1,125 @@
1
+ #
2
+ # h2. lib/imw/utils/extensions/array.rb -- array extensions
3
+ #
4
+ # == About
5
+ #
6
+ # Extensions to the +Array+ class.
7
+ #
8
+ # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
9
+ # Copyright:: Copyright (c) 2008 infochimps.org
10
+ # License:: GPL 3.0
11
+ # Website:: http://infinitemonkeywrench.org/
12
+ #
13
+ require 'active_support/core_ext/array/extract_options'
14
+ class Array #:nodoc:
15
+ include ActiveSupport::CoreExtensions::Array::ExtractOptions
16
+ end
17
+
18
+ class Array
19
+
20
+ # Return all but the last element
21
+ # This will be [] for both an empty array and a length-1 array
22
+ def most() self[0..-2] end
23
+
24
+ # Return all but the first element.
25
+ # This will be nil for an empty array and [] for a length-1 array
26
+ def rest() self[1..-1] end
27
+
28
+ # 'Un'-zip()s an array. Returns an array of arrays: the first array has the
29
+ # first element of each member, the second array has the second element of
30
+ # each member, and so on. Returns as many arrays as the first element in self
31
+ # and inserts a nil where the member array wasn't long enough.
32
+ #
33
+ # foo, bar = foo.zip(bar).unzip should leave foo and bar with the same values
34
+ # if foo and bar have the same length.
35
+ #
36
+ # Will fail on a not-array-of-arrays.
37
+ def unzip()
38
+ # An array of empty arrays, one for each vertical slot
39
+ vslices = self[0].map{ Array.new }
40
+ self.each do |hslice|
41
+ # push the elements of each array onto its slice.
42
+ vslices.zip(hslice).map{|vslice,h_el| vslice << h_el }
43
+ end
44
+ vslices
45
+ end
46
+
47
+ # Return a random element of this array.
48
+ def random_element
49
+ self[rand(self.length) - 1]
50
+ end
51
+
52
+ # convert an assoc (list of [key, val, [...]]'s) to a hash
53
+ def to_openstruct
54
+ mapped = {}
55
+ each{ |key,value| mapped[key] = value.to_openstruct }
56
+ OpenStruct.new(mapped)
57
+ end
58
+
59
+ # Return the elements of this array in a pretty-printed string,
60
+ # inserting +final_string+ between the last two items.
61
+ #
62
+ # >> [:one, :two, :three].quote_items_with "or"
63
+ # `one', `two', or `three'
64
+ #
65
+ def quote_items_with final_string = nil
66
+ string_items = self.map { |item| "`" + item.to_s + "'" }
67
+ case string_items.length
68
+ when 0
69
+ ""
70
+ when 1
71
+ string_items.first
72
+ when 2
73
+ if final_string then
74
+ string_items.join(" #{final_string} ")
75
+ else
76
+ string_items.join(', ')
77
+ end
78
+ else
79
+ string = string_items[0,string_items.length - 1].join ', '
80
+ if final_string then
81
+ string += ', ' + final_string + ' ' + string_items.last
82
+ else
83
+ string += ', ' + string_items.last
84
+ end
85
+ string
86
+ end
87
+ end
88
+
89
+ def in_groups_of(number, fill_with = nil, &block)
90
+ require 'enumerator'
91
+ collection = dup
92
+ collection << fill_with until collection.size.modulo(number).zero?
93
+ collection.each_slice(number, &block)
94
+ end
95
+
96
+ # Returns a single hash containing the merge of all hashes in this
97
+ # array. This is useful when dealing with badly written YAML files.
98
+ # Only merges hashes at depth zero, i.e. - this isn't recursive.
99
+ def merge_hashes
100
+ merged_hash = {}
101
+ self.each do |element|
102
+ merged_hash.merge!(element) if element.is_a?(Hash)
103
+ end
104
+ merged_hash
105
+ end
106
+
107
+ # Recurses through the elements of this Array collecting all String
108
+ # or Symbol "terminal" nodes.
109
+ def terminals &block
110
+ terminals = []
111
+ each do |element|
112
+ if element.respond_to? :terminals then
113
+ terminals += element.terminals
114
+ else
115
+ terminals << element
116
+ end
117
+ end
118
+ terminals.map! {|terminal| yield terminal } if block
119
+ terminals
120
+ end
121
+
122
+
123
+ end
124
+
125
+ # puts "#{File.basename(__FILE__)}: I have a loooong list of complaints. Firstly, ..." # at bottom
@@ -0,0 +1,8 @@
1
+ # :nodoc:
2
+ # for when cattr_accessor is all you need
3
+ #
4
+ require 'active_support/core_ext/array/extract_options'
5
+ class Array #:nodoc:
6
+ include ActiveSupport::CoreExtensions::Array::ExtractOptions
7
+ end
8
+ require 'active_support/core_ext/class/attribute_accessors'
@@ -0,0 +1,43 @@
1
+ #
2
+ # h2. lib/imw/utils/extensions/core.rb -- extensions to the Ruby core
3
+ #
4
+ # == About
5
+ #
6
+ # Some useful extensions to basic Ruby classes. This file is required
7
+ # by <tt>imw/utils</tt> so any files required here are automatically
8
+ # required when loading IMW.
9
+ #
10
+ # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
11
+ # Copyright:: Copyright (c) 2008 infochimps.org
12
+ # License:: GPL 3.0
13
+ # Website:: http://infinitemonkeywrench.org/
14
+ #
15
+ # puts "#{File.basename(__FILE__)}: Your monkeywrench does a complicated series of core-burning exercises and emerges with ripped, powerful-looking abs."
16
+
17
+ require 'imw/utils/extensions/string'
18
+ require 'imw/utils/extensions/array'
19
+ require 'imw/utils/extensions/hash'
20
+ require 'imw/utils/extensions/dir'
21
+ require 'imw/utils/extensions/struct'
22
+ require 'imw/utils/extensions/symbol'
23
+ require 'imw/utils/extensions/file_core'
24
+ require 'active_support/core_ext/module/aliasing'
25
+ require 'active_support/core_ext/object/blank'
26
+ require 'active_support/core_ext/object/misc'
27
+ #require 'active_support/core_ext/blank.rb'
28
+ require 'imw/utils/extensions/class/attribute_accessors'
29
+ # require 'ostruct'
30
+ require 'set'
31
+
32
+ module IMW
33
+ # A replacement for the standard system call which raises an
34
+ # IMW::SystemCallError if the command fails as well as printing the
35
+ # command appended to the end of <tt>error_message</tt>.
36
+ def self.system *commands
37
+ command = commands.flatten.join ' '
38
+ Kernel.system(command)
39
+ raise IMW::SystemCallError.new(command) unless $?.success?
40
+ end
41
+ end
42
+
43
+
@@ -0,0 +1,24 @@
1
+ #
2
+ # h2. lib/imw/utils/extensions/dir.rb -- directory extensions
3
+ #
4
+ # == About
5
+ #
6
+ # The Ruby +Dir+ module is rubbish. Time to clean it up a bit!
7
+ #
8
+ # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
9
+ # Copyright:: Copyright (c) 2008 infochimps.org
10
+ # License:: GPL 3.0
11
+ # Website:: http://infinitemonkeywrench.org/
12
+ #
13
+
14
+
15
+ class Dir
16
+
17
+ # Return the absolute paths of files and directories in the
18
+ # directory, leaving out `.' and `..' entries.
19
+ def abs_contents
20
+ self.entries.map {|entry| File.join(self.path,entry) unless entry == '.' || entry == '..'}.compact
21
+ end
22
+ end
23
+
24
+ # puts "#{File.basename(__FILE__)}: You open the folder and see along list of names. Some have been crossed out -- ominously..." # at bottom
@@ -0,0 +1,64 @@
1
+ #
2
+ # h2. lib/imw/utils/extensions/file.rb -- extensions to built-in file class
3
+ #
4
+ # == About
5
+ #
6
+ # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
7
+ # Copyright:: Copyright (c) 2008 infochimps.org
8
+ # License:: GPL 3.0
9
+ # Website:: http://infinitemonkeywrench.org/
10
+ #
11
+
12
+ require 'imw/utils/error'
13
+ require 'imw/utils/config'
14
+ require 'imw/utils/extensions/string'
15
+
16
+ class File
17
+
18
+ # Returns the name of the path given:
19
+ #
20
+ # File.name_of_file("/path/to/somefile.txt") => "somefile".
21
+ def self.name_of_file path
22
+ basename(path)[0,basename(path).length - extname(path).length]
23
+ end
24
+
25
+ # Returns what would be the handle of a source or dataset
26
+ # described by a file at +path+:
27
+ #
28
+ # File.handle "/path/to/a_particular_dataset.instructions.yaml" #=> :a_particular_dataset
29
+ def self.handle path
30
+ File.basename(path).split('.').first.handle
31
+ end
32
+
33
+ # Returns a unique (non-existing) version of the given +path+ by
34
+ # appending successive intgers, useful for copying files ito
35
+ # directories without clobbering existing files (a la <tt>wget
36
+ # -nc</tt>).
37
+ #
38
+ # In a directory <tt>/path/to</tt> without a file named
39
+ # <tt>data.txt</tt>
40
+ #
41
+ # File.uniquify("/path/to/data.txt") #=> "/path/to/data.txt"</tt>
42
+ #
43
+ # If <tt>data.txt</tt> were to already exist in that directory, then
44
+ #
45
+ # File.uniquify("/path/to/data.txt") #=> "/path/to/data.txt.1"
46
+ #
47
+ # If <tt>data.txt.1</tt> were to already exist then
48
+ #
49
+ # File.uniquify("/path/to/data.txt") #=> "/path/to/data.txt.2"
50
+ #
51
+ # and so on.
52
+ def self.uniquify path
53
+ orig_path = path.clone
54
+ copy_number = 1
55
+ while exist? path do
56
+ path = orig_path + ".#{copy_number}"
57
+ copy_number += 1
58
+ end
59
+ path
60
+ end
61
+
62
+ end
63
+
64
+ # puts "#{File.basename(__FILE__)}: You add a bit of glitter and jazz to all the folders in the cabinet. It makes you feel happier when you have to sort through them." # at bottom