picky 1.5.2 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/lib/picky/analyzer.rb +154 -0
  2. data/lib/picky/application.rb +53 -33
  3. data/lib/picky/character_substituters/west_european.rb +10 -6
  4. data/lib/picky/cli.rb +18 -18
  5. data/lib/picky/index/base.rb +44 -13
  6. data/lib/picky/index_bundle.rb +13 -4
  7. data/lib/picky/indexed/indexes.rb +26 -10
  8. data/lib/picky/indexing/indexes.rb +26 -24
  9. data/lib/picky/interfaces/live_parameters.rb +23 -16
  10. data/lib/picky/internals/extensions/object.rb +13 -6
  11. data/lib/picky/internals/frontend_adapters/rack.rb +30 -34
  12. data/lib/picky/internals/index/backend.rb +1 -2
  13. data/lib/picky/internals/index/file/basic.rb +18 -14
  14. data/lib/picky/internals/index/files.rb +16 -6
  15. data/lib/picky/internals/index/redis/basic.rb +12 -5
  16. data/lib/picky/internals/index/redis.rb +2 -2
  17. data/lib/picky/internals/indexed/bundle/base.rb +58 -14
  18. data/lib/picky/internals/indexed/bundle/memory.rb +40 -14
  19. data/lib/picky/internals/indexed/bundle/redis.rb +9 -30
  20. data/lib/picky/internals/indexed/categories.rb +19 -14
  21. data/lib/picky/internals/indexed/category.rb +44 -20
  22. data/lib/picky/internals/indexed/index.rb +23 -13
  23. data/lib/picky/internals/indexed/wrappers/bundle/wrapper.rb +27 -9
  24. data/lib/picky/internals/indexers/serial.rb +1 -1
  25. data/lib/picky/internals/indexing/bundle/base.rb +28 -28
  26. data/lib/picky/internals/indexing/bundle/memory.rb +14 -7
  27. data/lib/picky/internals/indexing/categories.rb +15 -11
  28. data/lib/picky/internals/indexing/category.rb +30 -20
  29. data/lib/picky/internals/indexing/index.rb +22 -14
  30. data/lib/picky/internals/query/allocations.rb +0 -15
  31. data/lib/picky/internals/query/combinations/base.rb +0 -4
  32. data/lib/picky/internals/query/combinations/redis.rb +19 -8
  33. data/lib/picky/internals/query/indexes.rb +3 -6
  34. data/lib/picky/internals/query/token.rb +0 -4
  35. data/lib/picky/internals/query/weights.rb +2 -11
  36. data/lib/picky/internals/results/base.rb +3 -10
  37. data/lib/picky/internals/tokenizers/base.rb +64 -28
  38. data/lib/picky/internals/tokenizers/index.rb +8 -8
  39. data/lib/picky/loader.rb +59 -53
  40. data/lib/picky/query/base.rb +23 -29
  41. data/lib/picky/sources/base.rb +10 -10
  42. data/lib/picky/sources/couch.rb +14 -10
  43. data/lib/picky/sources/csv.rb +21 -14
  44. data/lib/picky/sources/db.rb +37 -31
  45. data/lib/picky/sources/delicious.rb +11 -8
  46. data/lib/picky/sources/wrappers/base.rb +3 -1
  47. data/lib/picky/statistics.rb +66 -0
  48. data/lib/tasks/application.rake +3 -0
  49. data/lib/tasks/checks.rake +11 -0
  50. data/lib/tasks/framework.rake +3 -0
  51. data/lib/tasks/index.rake +9 -11
  52. data/lib/tasks/routes.rake +3 -2
  53. data/lib/tasks/shortcuts.rake +17 -5
  54. data/lib/tasks/statistics.rake +20 -12
  55. data/lib/tasks/try.rake +14 -14
  56. data/spec/lib/application_spec.rb +3 -3
  57. data/spec/lib/index/base_spec.rb +25 -3
  58. data/spec/lib/internals/extensions/object_spec.rb +46 -20
  59. data/spec/lib/internals/frontend_adapters/rack_spec.rb +3 -3
  60. data/spec/lib/internals/index/redis/basic_spec.rb +67 -0
  61. data/spec/lib/internals/indexers/serial_spec.rb +1 -1
  62. data/spec/lib/internals/results/base_spec.rb +0 -12
  63. data/spec/lib/internals/tokenizers/base_spec.rb +49 -1
  64. data/spec/lib/query/allocations_spec.rb +0 -56
  65. data/spec/lib/query/base_spec.rb +25 -21
  66. data/spec/lib/query/combinations/redis_spec.rb +6 -1
  67. data/spec/lib/sources/delicious_spec.rb +2 -2
  68. data/spec/lib/statistics_spec.rb +31 -0
  69. metadata +9 -2
@@ -20,12 +20,13 @@ module Query
20
20
  # Not directly instantiated. However, its methods are used by its subclasses, Full and Live.
21
21
  #
22
22
  class Base
23
-
23
+
24
24
  include Helpers::Measuring
25
-
25
+
26
+ attr_reader :indexes
26
27
  attr_writer :tokenizer, :identifiers_to_remove
27
28
  attr_accessor :reduce_to_amount, :weights
28
-
29
+
29
30
  # Takes:
30
31
  # * A number of indexes
31
32
  # * Options hash (optional) with:
@@ -40,7 +41,7 @@ module Query
40
41
  weights = options[:weights] || Weights.new
41
42
  @weights = Hash === weights ? Weights.new(weights) : weights
42
43
  end
43
-
44
+
44
45
  # Returns the right combinations strategy for
45
46
  # a number of query indexes.
46
47
  #
@@ -73,7 +74,7 @@ module Query
73
74
  def raise_different index_types
74
75
  raise DifferentTypesError.new(index_types)
75
76
  end
76
-
77
+
77
78
  # This is the main entry point for a query.
78
79
  # Use this in specs and also for running queries.
79
80
  #
@@ -86,22 +87,22 @@ module Query
86
87
  def search_with_text text, offset = 0
87
88
  search tokenized(text), offset
88
89
  end
89
-
90
+
90
91
  # Runs the actual search using Query::Tokens.
91
92
  #
92
93
  # Note: Internal method, use #search_with_text.
93
94
  #
94
95
  def search tokens, offset = 0
95
96
  results = nil
96
-
97
+
97
98
  duration = timed do
98
- results = execute(tokens, offset) || empty_results(offset) # TODO Does not work yet
99
+ results = execute tokens, offset
99
100
  end
100
101
  results.duration = duration.round 6
101
-
102
+
102
103
  results
103
104
  end
104
-
105
+
105
106
  # Execute a search using Query::Tokens.
106
107
  #
107
108
  # Note: Internal method, use #search_with_text.
@@ -109,16 +110,7 @@ module Query
109
110
  def execute tokens, offset
110
111
  result_type.from offset, sorted_allocations(tokens)
111
112
  end
112
-
113
- # Returns an empty result with default values.
114
- #
115
- # Parameters:
116
- # * offset = 0: _optional_ The offset to use for the empty results.
117
- #
118
- def empty_results offset = 0
119
- result_type.new offset
120
- end
121
-
113
+
122
114
  # Delegates the tokenizing to the query tokenizer.
123
115
  #
124
116
  # Parameters:
@@ -127,7 +119,7 @@ module Query
127
119
  def tokenized text
128
120
  @tokenizer.tokenize text
129
121
  end
130
-
122
+
131
123
  # Gets sorted allocations for the tokens.
132
124
  #
133
125
  # This generates the possible allocations, sorted.
@@ -144,27 +136,27 @@ module Query
144
136
  # TODO uniq, score, sort in there
145
137
  #
146
138
  allocations = @indexes.allocations_for tokens
147
-
139
+
148
140
  # Callbacks.
149
141
  #
150
142
  # TODO Reduce before sort?
151
143
  #
152
144
  reduce allocations
153
145
  remove_from allocations
154
-
146
+
155
147
  # Remove double allocations.
156
148
  #
157
149
  allocations.uniq
158
-
150
+
159
151
  # Score the allocations using weights as bias.
160
152
  #
161
153
  allocations.calculate_score weights
162
-
154
+
163
155
  # Sort the allocations.
164
156
  # (allocations are sorted according to score, highest to lowest)
165
157
  #
166
158
  allocations.sort
167
-
159
+
168
160
  # Return the allocations.
169
161
  #
170
162
  allocations
@@ -172,7 +164,7 @@ module Query
172
164
  def reduce allocations # :nodoc:
173
165
  allocations.reduce_to reduce_to_amount if reduce_to_amount
174
166
  end
175
-
167
+
176
168
  #
177
169
  #
178
170
  def remove_from allocations # :nodoc:
@@ -183,12 +175,14 @@ module Query
183
175
  def identifiers_to_remove # :nodoc:
184
176
  @identifiers_to_remove ||= []
185
177
  end
186
-
178
+
187
179
  # Display some nice information for the user.
188
180
  #
189
181
  def to_s
190
- s = "#{self.class}"
182
+ s = "#{self.class}("
183
+ s << @indexes.indexes.map(&:name).join(', ')
191
184
  s << ", weights: #{@weights}" unless @weights.empty?
185
+ s << ")"
192
186
  s
193
187
  end
194
188
 
@@ -7,12 +7,12 @@
7
7
  # * Delicious (http://del.icio.us, online bookmarking service)
8
8
  # See also:
9
9
  # http://github.com/floere/picky/wiki/Sources-Configuration
10
- #
10
+ #
11
11
  # Don't worry if your source isn't here. Adding your own is easy:
12
12
  # http://github.com/floere/picky/wiki/Contributing-sources
13
13
  #
14
14
  module Sources
15
-
15
+
16
16
  # Sources are where your data comes from.
17
17
  #
18
18
  # A source has 1 mandatory and 2 optional methods:
@@ -24,9 +24,9 @@ module Sources
24
24
  # Subclass this class <tt>class MySource < Base</tt> and override the methods in your source to do something.
25
25
  #
26
26
  class Base
27
-
27
+
28
28
  attr_reader :key_format
29
-
29
+
30
30
  # Connect to the backend.
31
31
  #
32
32
  # Called once per index/category combination before harvesting.
@@ -37,9 +37,9 @@ module Sources
37
37
  # * We open an file with data.
38
38
  #
39
39
  def connect_backend
40
-
40
+
41
41
  end
42
-
42
+
43
43
  # Called by the indexer when gathering data.
44
44
  #
45
45
  # Yields the data (id, text for id) for the given type and category.
@@ -51,7 +51,7 @@ module Sources
51
51
  def harvest index, category # :yields: id, text_for_id
52
52
  # This concrete implementation yields "nothing", override in subclasses.
53
53
  end
54
-
54
+
55
55
  # Used to take a snapshot of your data if it is fast changing.
56
56
  #
57
57
  # Called once for each type before harvesting.
@@ -60,9 +60,9 @@ module Sources
60
60
  # * In a DB source, a table based on the source's select statement is created.
61
61
  #
62
62
  def take_snapshot index
63
-
63
+
64
64
  end
65
-
65
+
66
66
  end
67
-
67
+
68
68
  end
@@ -1,12 +1,12 @@
1
1
  module Sources
2
-
2
+
3
3
  # Raised when a Couch source is instantiated without a file.
4
4
  #
5
5
  # Example:
6
6
  # Sources::Couch.new(:column1, :column2) # without file option
7
7
  #
8
8
  class NoCouchDBGiven < StandardError; end
9
-
9
+
10
10
  # A Couch database source.
11
11
  #
12
12
  # Options:
@@ -19,28 +19,32 @@ module Sources
19
19
  # Sources::Couch.new(:title, :author, :isbn, url:'localhost:5984', user:'someuser', password:'somepassword')
20
20
  #
21
21
  class Couch < Base
22
-
22
+
23
23
  #
24
24
  #
25
25
  def initialize *category_names, options
26
26
  check_gem
27
-
27
+
28
28
  Hash === options && options[:url] || raise_no_db_given(category_names)
29
29
  @db = RestClient::Resource.new options.delete(:url), options
30
30
  end
31
-
32
- # Default key format method for couch DB is to_sym.
31
+
32
+ def to_s
33
+ self.class.name
34
+ end
35
+
36
+ # Default key format method for couch DB is to_sym.
33
37
  #
34
38
  def key_format
35
39
  :to_sym
36
40
  end
37
-
41
+
38
42
  # Tries to require the rest_client gem.
39
43
  #
40
44
  def check_gem # :nodoc:
41
45
  require 'rest_client'
42
46
  rescue LoadError
43
- puts_gem_missing 'rest-client', 'the CouchDB source'
47
+ warn_gem_missing 'rest-client', 'the CouchDB source'
44
48
  exit 1
45
49
  end
46
50
 
@@ -55,14 +59,14 @@ module Sources
55
59
  yield doc[@@id_key], doc[category_name] || next
56
60
  end
57
61
  end
58
-
62
+
59
63
  def get_data &block # :nodoc:
60
64
  resp = @db['_all_docs?include_docs=true'].get
61
65
  JSON.parse(resp)['rows'].
62
66
  map{|row| row['doc']}.
63
67
  each &block
64
68
  end
65
-
69
+
66
70
  def raise_no_db_given category_names # :nodoc:
67
71
  raise NoCouchDBGiven.new(category_names.join(', '))
68
72
  end
@@ -1,14 +1,14 @@
1
1
  module Sources
2
-
2
+
3
3
  # Raised when a CSV source is instantiated without a file.
4
4
  #
5
5
  # Example:
6
6
  # Sources::CSV.new(:column1, :column2) # without file option
7
7
  #
8
8
  class NoCSVFileGiven < StandardError; end
9
-
9
+
10
10
  # Describes a CSV source, a file with comma separated values in it.
11
- #
11
+ #
12
12
  # The first column is implicitly assumed to be the id column.
13
13
  #
14
14
  # It takes the same options as the Ruby 1.9 CSV class.
@@ -19,36 +19,43 @@ module Sources
19
19
  # Sources::CSV.new(:title, :author, :isbn, file:'data/a_csv_file.csv', row_sep:"\n")
20
20
  #
21
21
  class CSV < Base
22
-
22
+
23
23
  # The CSV file's path, relative to PICKY_ROOT.
24
24
  #
25
25
  attr_reader :file_name
26
-
26
+
27
27
  # The options that were passed into #new.
28
28
  #
29
29
  attr_reader :csv_options, :key_format
30
-
30
+
31
31
  # The data category names.
32
32
  #
33
33
  attr_reader :category_names
34
-
34
+
35
35
  def initialize *category_names, options
36
36
  require 'csv'
37
37
  @category_names = category_names
38
-
38
+
39
39
  @csv_options = Hash === options && options || {}
40
40
  @file_name = @csv_options.delete(:file) || raise_no_file_given(category_names)
41
-
41
+
42
42
  key_format = options.delete :key_format
43
43
  @key_format = key_format && key_format.to_sym || :to_i
44
44
  end
45
-
45
+
46
+ def to_s
47
+ parameters = category_names
48
+ parameters << { file: file_name }
49
+ parameters << csv_options unless csv_options.empty?
50
+ %Q{#{self.class.name}(#{parameters.join(', ')})}
51
+ end
52
+
46
53
  # Raises a NoCSVFileGiven exception.
47
54
  #
48
55
  def raise_no_file_given category_names # :nodoc:
49
56
  raise NoCSVFileGiven.new(category_names.join(', '))
50
57
  end
51
-
58
+
52
59
  # Harvests the data to index.
53
60
  #
54
61
  def harvest _, category
@@ -61,13 +68,13 @@ module Sources
61
68
  yield indexed_id, text
62
69
  end
63
70
  end
64
-
71
+
65
72
  #
66
73
  #
67
74
  def get_data &block # :nodoc:
68
75
  ::CSV.foreach file_name, csv_options, &block
69
76
  end
70
-
77
+
71
78
  end
72
-
79
+
73
80
  end
@@ -1,5 +1,5 @@
1
1
  module Sources
2
-
2
+
3
3
  # Describes a database source. Needs a SELECT statement
4
4
  # (with id in it), and a file option or the options from an AR config file.
5
5
  #
@@ -15,27 +15,33 @@ module Sources
15
15
  # Sources::DB.new('SELECT id, title, author, year FROM books', adapter: 'mysql', host:'localhost', ...)
16
16
  #
17
17
  class DB < Base
18
-
18
+
19
19
  # The select statement that was passed in.
20
20
  #
21
21
  attr_reader :select_statement
22
-
22
+
23
23
  # The database adapter.
24
24
  #
25
25
  attr_reader :database
26
-
26
+
27
27
  # The database connection options that were either passed in or loaded from the given file.
28
28
  #
29
- attr_reader :connection_options
30
-
29
+ attr_reader :connection_options, :options
30
+
31
31
  @@traversal_id = :__picky_id
32
-
32
+
33
33
  def initialize select_statement, options = { file: 'app/db.yml' }
34
34
  @select_statement = select_statement
35
35
  @database = create_database_adapter
36
36
  @options = options
37
37
  end
38
-
38
+
39
+ def to_s
40
+ parameters = [select_statement.inspect]
41
+ parameters << options unless options.empty?
42
+ %Q{#{self.class.name}(#{parameters.join(', ')})}
43
+ end
44
+
39
45
  # Creates a database adapter for use with this source.
40
46
  def create_database_adapter # :nodoc:
41
47
  # TODO Do not use ActiveRecord directly.
@@ -46,7 +52,7 @@ module Sources
46
52
  adapter_class.abstract_class = true
47
53
  adapter_class
48
54
  end
49
-
55
+
50
56
  # Configure the backend.
51
57
  #
52
58
  # Options:
@@ -63,7 +69,7 @@ module Sources
63
69
  end
64
70
  self
65
71
  end
66
-
72
+
67
73
  # Connect the backend.
68
74
  #
69
75
  # Will raise unless connection options have been given.
@@ -73,64 +79,64 @@ module Sources
73
79
  raise "Database backend not configured" unless connection_options
74
80
  database.establish_connection connection_options
75
81
  end
76
-
82
+
77
83
  # Take a snapshot of the data.
78
84
  #
79
85
  # Uses CREATE TABLE AS with the given SELECT statement to create a snapshot of the data.
80
86
  #
81
87
  def take_snapshot index
82
88
  connect_backend
83
-
89
+
84
90
  origin = snapshot_table_name index
85
91
  on_database = database.connection
86
-
92
+
87
93
  # Drop the table if it exists.
88
94
  #
89
95
  on_database.drop_table origin if on_database.table_exists?(origin)
90
-
96
+
91
97
  # The adapters currently do not support this.
92
98
  #
93
99
  on_database.execute "CREATE TABLE #{origin} AS #{select_statement}"
94
-
100
+
95
101
  # Add a column that Picky uses to traverse the table's entries.
96
102
  #
97
103
  on_database.add_column origin, @@traversal_id, :primary_key, :null => :false
98
-
104
+
99
105
  # Execute any special queries this index needs executed.
100
106
  #
101
107
  on_database.execute index.after_indexing if index.after_indexing
102
108
  end
103
-
109
+
104
110
  # Counts all the entries that are used for the index.
105
111
  #
106
112
  def count index
107
113
  connect_backend
108
-
114
+
109
115
  database.connection.select_value("SELECT COUNT(#{@@traversal_id}) FROM #{snapshot_table_name(index)}").to_i
110
116
  end
111
-
117
+
112
118
  # The name of the snapshot table created by Picky.
113
119
  #
114
120
  def snapshot_table_name index
115
121
  "picky_#{index.name}_index"
116
122
  end
117
-
123
+
118
124
  # Harvests the data to index in chunks.
119
125
  #
120
126
  def harvest index, category, &block
121
127
  connect_backend
122
-
128
+
123
129
  (0..count(index)).step(chunksize) do |offset|
124
130
  get_data index, category, offset, &block
125
131
  end
126
132
  end
127
-
133
+
128
134
  # Gets the data from the backend.
129
135
  #
130
136
  def get_data index, category, offset, &block # :nodoc:
131
-
137
+
132
138
  select_statement = harvest_statement_with_offset index, category, offset
133
-
139
+
134
140
  # TODO Rewrite ASAP.
135
141
  #
136
142
  if database.connection.adapter_name == "PostgreSQL"
@@ -146,29 +152,29 @@ module Sources
146
152
  end
147
153
  end
148
154
  end
149
-
155
+
150
156
  # Builds a harvest statement for getting data to index.
151
157
  #
152
158
  def harvest_statement_with_offset index, category, offset
153
159
  statement = harvest_statement index, category
154
-
160
+
155
161
  statement += statement.include?('WHERE') ? ' AND' : ' WHERE'
156
-
162
+
157
163
  "#{statement} st.#{@@traversal_id} > #{offset} LIMIT #{chunksize}"
158
164
  end
159
-
165
+
160
166
  # The harvest statement used to pull data from the snapshot table.
161
167
  #
162
168
  def harvest_statement index, category
163
169
  "SELECT id, #{category.from} FROM #{snapshot_table_name(index)} st"
164
170
  end
165
-
171
+
166
172
  # The amount of records that are loaded each chunk.
167
173
  #
168
174
  def chunksize
169
175
  25_000
170
176
  end
171
-
177
+
172
178
  end
173
-
179
+
174
180
  end
@@ -1,5 +1,5 @@
1
1
  module Sources
2
-
2
+
3
3
  # Describes a Delicious (http://deli.cio.us) source.
4
4
  #
5
5
  # This source has a fixed set of categories:
@@ -11,7 +11,7 @@ module Sources
11
11
  # Sources::CSV.new('usrnam', 'paswrd')
12
12
  #
13
13
  class Delicious < Base
14
-
14
+
15
15
  def initialize username, password
16
16
  check_gem
17
17
  @username = username
@@ -20,21 +20,24 @@ module Sources
20
20
  def check_gem # :nodoc:
21
21
  require 'www/delicious'
22
22
  rescue LoadError
23
- puts_gem_missing 'www-delicious', 'the delicious source'
23
+ warn_gem_missing 'www-delicious', 'the delicious source'
24
24
  exit 1
25
25
  end
26
-
26
+
27
+ def to_s
28
+ self.class.name
29
+ end
30
+
27
31
  # Harvests the data to index.
28
32
  #
29
33
  def harvest _, category
30
34
  get_data do |indexed_id, data|
31
35
  text = data[category.from]
32
36
  next unless text
33
- text.force_encoding 'utf-8' # TODO Still needed?
34
37
  yield indexed_id, text
35
38
  end
36
39
  end
37
-
40
+
38
41
  #
39
42
  #
40
43
  def get_data # :nodoc:
@@ -50,7 +53,7 @@ module Sources
50
53
  yield @generated_id, data
51
54
  end
52
55
  end
53
-
56
+
54
57
  end
55
-
58
+
56
59
  end
@@ -1,6 +1,8 @@
1
1
  module Sources
2
2
 
3
- # TODO Document.
3
+ # Source wrappers can be used to rewrite data before it goes into the index.
4
+ #
5
+ # For example if you want to normalize data.
4
6
  #
5
7
  module Wrappers # :nodoc:all
6
8
 
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+ #
3
+
4
+ # Gathers different statistics
5
+ # when methods are called.
6
+ #
7
+ # Can be output using to_s.
8
+ #
9
+ class Statistics
10
+
11
+ def self.instance
12
+ @statistics ||= new
13
+ end
14
+
15
+ def preamble
16
+ loc = lines_of_code File.open('app/application.rb').read
17
+
18
+ @preamble ||= <<-PREAMBLE
19
+ \033[1mApplication(s)\033[m
20
+ Definition LOC: #{"%4d" % loc}
21
+ Indexes defined: #{"%4d" % Indexes.size}
22
+ PREAMBLE
23
+ end
24
+
25
+ # Gathers information about the application.
26
+ #
27
+ def application
28
+ preamble
29
+ @application = Application.apps.map &:indented_to_s
30
+ end
31
+
32
+ # Gathers information about the indexes.
33
+ #
34
+ def analyze
35
+ preamble
36
+
37
+ @indexes = ["\033[1mIndexes analysis\033[m:"]
38
+ Indexes.analyze.each_pair do |name, index|
39
+ @indexes << <<-ANALYSIS
40
+ #{"#{name}:".indented_to_s}:
41
+ #{"exact:\n#{index[:exact].indented_to_s}".indented_to_s(4)}
42
+ #{"partial*:\n#{index[:partial].indented_to_s}".indented_to_s(4)}
43
+ ANALYSIS
44
+ end
45
+ @indexes = @indexes.join "\n"
46
+ end
47
+
48
+ # Outputs all gathered statistics.
49
+ #
50
+ def to_s
51
+ <<-STATS
52
+
53
+ Picky Configuration:
54
+
55
+ #{[@preamble, @application, @indexes].compact.join("\n")}
56
+ STATS
57
+ end
58
+
59
+ # Internal methods.
60
+ #
61
+
62
+ def lines_of_code text
63
+ text.scan(/^\s*[^#\s].*$/).size
64
+ end
65
+
66
+ end
@@ -1,4 +1,7 @@
1
1
  # desc "Loads the application, including its configuration."
2
+ #
3
+ # Note: This is used by tasks to load the application (and the framework) as a dependency.
4
+ #
2
5
  task :application => :framework do
3
6
  Loader.load_application
4
7
  end
@@ -0,0 +1,11 @@
1
+ # Checks to help the user.
2
+ #
3
+ namespace :check do
4
+
5
+ desc 'Checks the index files for files that are suspiciously small or missing.'
6
+ task :index => :application do
7
+ Indexes.check_caches
8
+ puts "All indexes look ok."
9
+ end
10
+
11
+ end
@@ -1,4 +1,7 @@
1
1
  # desc "Loads the framework."
2
+ #
3
+ # Note: This is used by tasks to load the framework as a dependency.
4
+ #
2
5
  task :framework do
3
6
  require File.expand_path '../../picky', __FILE__
4
7
  end