Class: Pincerna::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/pincerna/base.rb

Overview

Base class for all filter.

Direct Known Subclasses

Bookmark, CurrencyConversion, Ip, Map, Translation, UnitConversion, Vpn, Weather

Constant Summary

MATCHER =

The expression to match.

/^(?<all>.*)$/i
RELEVANT_MATCHES =

Relevant groups in the match.

{
  "all" => ->(_, value) { value }
}
TYPES =

Recognized types of filtering

{
  "unit_conversion" => /^(convert|unit|c)$/,
  "currency_conversion" => /^(currency|cc)$/,
  "translation" => /^(translate|t)$/,
  "map" => /^(map|m)$/,
  "weather" => /^(forecast|weather)$/,
  "ip" => /^ip$/,
  "vpn" => /^vpn$/,
  "chrome_bookmark" => /^(chrome-bookmark|bc)$/,
  "safari_bookmark" => /^(safari-bookmark|bs)$/,
  "firefox_bookmark" => /^(firefox-bookmark|bf)$/
}
FULL_NAME =

The full name of the gem

"it.cowtech.pincerna"
ROOT =

The root of the pincerna gem

File.expand_path(File.dirname(__FILE__) + "/../../")
CACHE_ROOT =

The root of the cache

File.expand_path("~/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/#{FULL_NAME}")
WORKFLOW_ROOT =

The root of alfred workflows

File.expand_path("~/Library/Application Support/Alfred 2/Alfred.alfredpreferences/workflows/#{FULL_NAME}")

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Base) initialize(query, requested_format = "xml", debug = nil)

Creates a new query.

Parameters:

  • query (String)

    The argument of the query.

  • requested_format (String) (defaults to: "xml")

    The format to use. Valid values are xml (default), yaml or yml.

  • debug (String) (defaults to: nil)

    The debug mode.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/pincerna/base.rb', line 85

def initialize(query, requested_format = "xml", debug = nil)
  @query = query.strip.gsub("\\ ", " ")
  @cache_dir = CACHE_ROOT

  if requested_format =~ /^y(a?)ml$/i then
    @format = :yml
    @format_content_type = "text/x-yaml"
  else
    @format = :xml
    @format_content_type = "text/xml"
  end

  @debug = debug
  @feedback_items = []
end

Instance Attribute Details

- (Symbol) format (readonly)

Returns The format of output. Can be :xml (default) or :yml.

Returns:

  • (Symbol)

    The format of output. Can be :xml (default) or :yml.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/pincerna/base.rb', line 17

class Base
  # The expression to match.
  MATCHER = /^(?<all>.*)$/i

  # Relevant groups in the match.
  RELEVANT_MATCHES = {
    "all" => ->(_, value) { value }
  }

  # Recognized types of filtering
  TYPES = {
    "unit_conversion" => /^(convert|unit|c)$/,
    "currency_conversion" => /^(currency|cc)$/,
    "translation" => /^(translate|t)$/,
    "map" => /^(map|m)$/,
    "weather" => /^(forecast|weather)$/,
    "ip" => /^ip$/,
    "vpn" => /^vpn$/,
    "chrome_bookmark" => /^(chrome-bookmark|bc)$/,
    "safari_bookmark" => /^(safari-bookmark|bs)$/,
    "firefox_bookmark" => /^(firefox-bookmark|bf)$/
  }

  # The full name of the gem
  FULL_NAME = "it.cowtech.pincerna"

  # The root of the pincerna gem
  ROOT = File.expand_path(File.dirname(__FILE__) + "/../../")

  # The root of the cache
  CACHE_ROOT = File.expand_path("~/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/#{FULL_NAME}")

  # The root of alfred workflows
  WORKFLOW_ROOT = File.expand_path("~/Library/Application Support/Alfred 2/Alfred.alfredpreferences/workflows/#{FULL_NAME}")

  attr_reader :output, :format, :format_content_type

  # Executes a filtering query.
  #
  # @param type [Symbol] The type of the query.
  # @param query [String] The argument of the query.
  # @param format [String] The format to use. Valid values are `:xml` (default) and `:yml`.
  # @param debug [String] The debug mode.
  # @return [String] The result of the query.
  def self.execute!(type, query, format = :xml, debug = nil)
    instance = nil

    type = catch(:type) do
      TYPES.each do |file, matcher|
        throw(:type, file) if type =~ matcher
      end

      nil
    end

    if type
      instance = find_class(type).new(query, format, debug)
      instance.filter
    end

    instance
  end

  # Creates a new query.
  #
  # @param query [String] The argument of the query.
  # @param requested_format [String] The format to use. Valid values are `xml` (default), `yaml` or `yml`.
  # @param debug [String] The debug mode.
  def initialize(query, requested_format = "xml", debug = nil)
    @query = query.strip.gsub("\\ ", " ")
    @cache_dir = CACHE_ROOT

    if requested_format =~ /^y(a?)ml$/i then
      @format = :yml
      @format_content_type = "text/x-yaml"
    else
      @format = :xml
      @format_content_type = "text/xml"
    end

    @debug = debug
    @feedback_items = []
  end

  # Filters a query.
  #
  # @return [String] The feedback items of the query, formatted as XML.
  def filter
    # Match the query
    matches = self.class::MATCHER.match(@query)

    if matches then
      # Execute the filtering
      results = execute_filtering(matches)

      # Show results if appropriate
      process_results(results).each {|r| add_feedback_item(r) } if !results.empty?
    end

    output_feedback
  end

  # Filters a query.
  #
  # @param args [Array] The arguments of the query.
  # @return [Array] A list of items to process.
  def perform_filtering(*args)
    raise ArgumentError.new("Must be overriden by subclasses.")
  end

  # Processes items to obtain feedback items.
  #
  # @param results [Array] The items to process.
  # @return [Array] The feedback items.
  def process_results(results)
    raise ArgumentError.new("Must be overriden by subclasses.")
  end

  # Adds a feedback items.
  #
  # @param item [Array] The items to add.
  def add_feedback_item(item)
    @feedback_items << item
  end

  # Prepares the feedback for output.
  def output_feedback
    if format == :xml then
      @output = Nokogiri::XML::Builder.new { |xml|
        xml.items do
          @feedback_items.each do |item|
            childs, attributes = split_output_item(item)

            xml.item(attributes) do
              childs.each { |name, value| xml.send(name, value) }
            end
          end
        end
      }.to_xml
    else
      @output = @feedback_items.to_yaml
    end
  end

  # Rounds a float to a certain precision.
  #
  # @param value [Float] The value to convert.
  # @param precision [Fixnum] The precision to use.
  # @return [Float] The rounded value.
  def round_float(value, precision = 3)
    factor = 10**precision
    (value * factor).round.to_f / factor
  end

  # Rounds a float to a certain precision and prints it as a string. Unneeded leading zero are remove.
  #
  # @param value [Float] The value to print.
  # @param precision [Fixnum] The precision to use.
  # @return [String] The formatted value.
  def format_float(value, precision = 3)
    value = round_float(value, precision)
    value = "%0.#{precision}f" % value
    value.gsub(/\.?0+$/, "")
  end

  protected
    # Instantiates the new class.
    #
    # @param file [String] The file name.
    def self.find_class(file)
      Pincerna.const_get(file.capitalize.gsub(/_(.)/) { $1.upcase})
    end

    # Executes filtering on matched data.
    #
    # @param matches [MatchData] The matched data.
    # @return [Array] The results of filtering.
    def execute_filtering(matches)
      # Get relevant matches and arguments.
      relevant = self.class::RELEVANT_MATCHES
      args = relevant.map {|key, value| value.call(self, matches[key]) }

      # Now perform the operation
      begin
        perform_filtering(*args)
      rescue => e
        raise e if debug_mode == :error
        []
      end
    end

    # Converts an array of key-value pairs to an hash.
    #
    # @param array [Array] The array to convert.
    # @return [Hash] The converted hash.
    def array_to_hash(array)
      array.reduce({}){ |rv, entry|
        rv[entry[0]] = entry[1]
        rv
      }
    end

    # Gets attributes and children for output.
    #
    # @param item [Hash] The output item.
    # @return [Array] An array with children and attributes.
    def split_output_item(item)
      item.partition {|k, _| [:title, :subtitle, :icon].include?(k) }.map {|a| array_to_hash(a) }
    end

    # Fetches remote JSON resource.
    #
    # @param url [String] The URL to get.
    # @param params [Hash] The parameters to pass to the server.
    # @param json [Boolean] If the response is a JSON object.
    # @return [Hash] The server's response.
    def fetch_remote_resource(url, params, json = true)
      args = {query: params}
      args[:head] = {"accept" => "application/json"} if json
      response = EM::HttpRequest.new(url, {connect_timeout: 5}).get(args).response
      json ? Oj.load(response) : response
    end

    # Executes a command and returns its output.
    #
    # @param args [Array] The command to execute, with its arguments.
    # @return [String] The output of the command
    def execute_command(*args)
      rv = ""
      IO.popen(args) {|f| rv = f.read }
      rv
    end

    # Returns the current debug mode.
    #
    # @return [Boolean|NilClass] The current debug mode.
    def debug_mode
      mode = ENV["PINCERNA_DEBUG"] || @debug
      !mode.nil? ? mode.to_sym : nil
    end
end

- (String) format_content_type (readonly)

Returns The content type of the format. Can be text/xml (default) or text/x-yaml.

Returns:

  • (String)

    The content type of the format. Can be text/xml (default) or text/x-yaml.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/pincerna/base.rb', line 17

class Base
  # The expression to match.
  MATCHER = /^(?<all>.*)$/i

  # Relevant groups in the match.
  RELEVANT_MATCHES = {
    "all" => ->(_, value) { value }
  }

  # Recognized types of filtering
  TYPES = {
    "unit_conversion" => /^(convert|unit|c)$/,
    "currency_conversion" => /^(currency|cc)$/,
    "translation" => /^(translate|t)$/,
    "map" => /^(map|m)$/,
    "weather" => /^(forecast|weather)$/,
    "ip" => /^ip$/,
    "vpn" => /^vpn$/,
    "chrome_bookmark" => /^(chrome-bookmark|bc)$/,
    "safari_bookmark" => /^(safari-bookmark|bs)$/,
    "firefox_bookmark" => /^(firefox-bookmark|bf)$/
  }

  # The full name of the gem
  FULL_NAME = "it.cowtech.pincerna"

  # The root of the pincerna gem
  ROOT = File.expand_path(File.dirname(__FILE__) + "/../../")

  # The root of the cache
  CACHE_ROOT = File.expand_path("~/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/#{FULL_NAME}")

  # The root of alfred workflows
  WORKFLOW_ROOT = File.expand_path("~/Library/Application Support/Alfred 2/Alfred.alfredpreferences/workflows/#{FULL_NAME}")

  attr_reader :output, :format, :format_content_type

  # Executes a filtering query.
  #
  # @param type [Symbol] The type of the query.
  # @param query [String] The argument of the query.
  # @param format [String] The format to use. Valid values are `:xml` (default) and `:yml`.
  # @param debug [String] The debug mode.
  # @return [String] The result of the query.
  def self.execute!(type, query, format = :xml, debug = nil)
    instance = nil

    type = catch(:type) do
      TYPES.each do |file, matcher|
        throw(:type, file) if type =~ matcher
      end

      nil
    end

    if type
      instance = find_class(type).new(query, format, debug)
      instance.filter
    end

    instance
  end

  # Creates a new query.
  #
  # @param query [String] The argument of the query.
  # @param requested_format [String] The format to use. Valid values are `xml` (default), `yaml` or `yml`.
  # @param debug [String] The debug mode.
  def initialize(query, requested_format = "xml", debug = nil)
    @query = query.strip.gsub("\\ ", " ")
    @cache_dir = CACHE_ROOT

    if requested_format =~ /^y(a?)ml$/i then
      @format = :yml
      @format_content_type = "text/x-yaml"
    else
      @format = :xml
      @format_content_type = "text/xml"
    end

    @debug = debug
    @feedback_items = []
  end

  # Filters a query.
  #
  # @return [String] The feedback items of the query, formatted as XML.
  def filter
    # Match the query
    matches = self.class::MATCHER.match(@query)

    if matches then
      # Execute the filtering
      results = execute_filtering(matches)

      # Show results if appropriate
      process_results(results).each {|r| add_feedback_item(r) } if !results.empty?
    end

    output_feedback
  end

  # Filters a query.
  #
  # @param args [Array] The arguments of the query.
  # @return [Array] A list of items to process.
  def perform_filtering(*args)
    raise ArgumentError.new("Must be overriden by subclasses.")
  end

  # Processes items to obtain feedback items.
  #
  # @param results [Array] The items to process.
  # @return [Array] The feedback items.
  def process_results(results)
    raise ArgumentError.new("Must be overriden by subclasses.")
  end

  # Adds a feedback items.
  #
  # @param item [Array] The items to add.
  def add_feedback_item(item)
    @feedback_items << item
  end

  # Prepares the feedback for output.
  def output_feedback
    if format == :xml then
      @output = Nokogiri::XML::Builder.new { |xml|
        xml.items do
          @feedback_items.each do |item|
            childs, attributes = split_output_item(item)

            xml.item(attributes) do
              childs.each { |name, value| xml.send(name, value) }
            end
          end
        end
      }.to_xml
    else
      @output = @feedback_items.to_yaml
    end
  end

  # Rounds a float to a certain precision.
  #
  # @param value [Float] The value to convert.
  # @param precision [Fixnum] The precision to use.
  # @return [Float] The rounded value.
  def round_float(value, precision = 3)
    factor = 10**precision
    (value * factor).round.to_f / factor
  end

  # Rounds a float to a certain precision and prints it as a string. Unneeded leading zero are remove.
  #
  # @param value [Float] The value to print.
  # @param precision [Fixnum] The precision to use.
  # @return [String] The formatted value.
  def format_float(value, precision = 3)
    value = round_float(value, precision)
    value = "%0.#{precision}f" % value
    value.gsub(/\.?0+$/, "")
  end

  protected
    # Instantiates the new class.
    #
    # @param file [String] The file name.
    def self.find_class(file)
      Pincerna.const_get(file.capitalize.gsub(/_(.)/) { $1.upcase})
    end

    # Executes filtering on matched data.
    #
    # @param matches [MatchData] The matched data.
    # @return [Array] The results of filtering.
    def execute_filtering(matches)
      # Get relevant matches and arguments.
      relevant = self.class::RELEVANT_MATCHES
      args = relevant.map {|key, value| value.call(self, matches[key]) }

      # Now perform the operation
      begin
        perform_filtering(*args)
      rescue => e
        raise e if debug_mode == :error
        []
      end
    end

    # Converts an array of key-value pairs to an hash.
    #
    # @param array [Array] The array to convert.
    # @return [Hash] The converted hash.
    def array_to_hash(array)
      array.reduce({}){ |rv, entry|
        rv[entry[0]] = entry[1]
        rv
      }
    end

    # Gets attributes and children for output.
    #
    # @param item [Hash] The output item.
    # @return [Array] An array with children and attributes.
    def split_output_item(item)
      item.partition {|k, _| [:title, :subtitle, :icon].include?(k) }.map {|a| array_to_hash(a) }
    end

    # Fetches remote JSON resource.
    #
    # @param url [String] The URL to get.
    # @param params [Hash] The parameters to pass to the server.
    # @param json [Boolean] If the response is a JSON object.
    # @return [Hash] The server's response.
    def fetch_remote_resource(url, params, json = true)
      args = {query: params}
      args[:head] = {"accept" => "application/json"} if json
      response = EM::HttpRequest.new(url, {connect_timeout: 5}).get(args).response
      json ? Oj.load(response) : response
    end

    # Executes a command and returns its output.
    #
    # @param args [Array] The command to execute, with its arguments.
    # @return [String] The output of the command
    def execute_command(*args)
      rv = ""
      IO.popen(args) {|f| rv = f.read }
      rv
    end

    # Returns the current debug mode.
    #
    # @return [Boolean|NilClass] The current debug mode.
    def debug_mode
      mode = ENV["PINCERNA_DEBUG"] || @debug
      !mode.nil? ? mode.to_sym : nil
    end
end

- (String) output (readonly)

Returns The output of filtering.

Returns:

  • (String)

    The output of filtering.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/pincerna/base.rb', line 17

class Base
  # The expression to match.
  MATCHER = /^(?<all>.*)$/i

  # Relevant groups in the match.
  RELEVANT_MATCHES = {
    "all" => ->(_, value) { value }
  }

  # Recognized types of filtering
  TYPES = {
    "unit_conversion" => /^(convert|unit|c)$/,
    "currency_conversion" => /^(currency|cc)$/,
    "translation" => /^(translate|t)$/,
    "map" => /^(map|m)$/,
    "weather" => /^(forecast|weather)$/,
    "ip" => /^ip$/,
    "vpn" => /^vpn$/,
    "chrome_bookmark" => /^(chrome-bookmark|bc)$/,
    "safari_bookmark" => /^(safari-bookmark|bs)$/,
    "firefox_bookmark" => /^(firefox-bookmark|bf)$/
  }

  # The full name of the gem
  FULL_NAME = "it.cowtech.pincerna"

  # The root of the pincerna gem
  ROOT = File.expand_path(File.dirname(__FILE__) + "/../../")

  # The root of the cache
  CACHE_ROOT = File.expand_path("~/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/#{FULL_NAME}")

  # The root of alfred workflows
  WORKFLOW_ROOT = File.expand_path("~/Library/Application Support/Alfred 2/Alfred.alfredpreferences/workflows/#{FULL_NAME}")

  attr_reader :output, :format, :format_content_type

  # Executes a filtering query.
  #
  # @param type [Symbol] The type of the query.
  # @param query [String] The argument of the query.
  # @param format [String] The format to use. Valid values are `:xml` (default) and `:yml`.
  # @param debug [String] The debug mode.
  # @return [String] The result of the query.
  def self.execute!(type, query, format = :xml, debug = nil)
    instance = nil

    type = catch(:type) do
      TYPES.each do |file, matcher|
        throw(:type, file) if type =~ matcher
      end

      nil
    end

    if type
      instance = find_class(type).new(query, format, debug)
      instance.filter
    end

    instance
  end

  # Creates a new query.
  #
  # @param query [String] The argument of the query.
  # @param requested_format [String] The format to use. Valid values are `xml` (default), `yaml` or `yml`.
  # @param debug [String] The debug mode.
  def initialize(query, requested_format = "xml", debug = nil)
    @query = query.strip.gsub("\\ ", " ")
    @cache_dir = CACHE_ROOT

    if requested_format =~ /^y(a?)ml$/i then
      @format = :yml
      @format_content_type = "text/x-yaml"
    else
      @format = :xml
      @format_content_type = "text/xml"
    end

    @debug = debug
    @feedback_items = []
  end

  # Filters a query.
  #
  # @return [String] The feedback items of the query, formatted as XML.
  def filter
    # Match the query
    matches = self.class::MATCHER.match(@query)

    if matches then
      # Execute the filtering
      results = execute_filtering(matches)

      # Show results if appropriate
      process_results(results).each {|r| add_feedback_item(r) } if !results.empty?
    end

    output_feedback
  end

  # Filters a query.
  #
  # @param args [Array] The arguments of the query.
  # @return [Array] A list of items to process.
  def perform_filtering(*args)
    raise ArgumentError.new("Must be overriden by subclasses.")
  end

  # Processes items to obtain feedback items.
  #
  # @param results [Array] The items to process.
  # @return [Array] The feedback items.
  def process_results(results)
    raise ArgumentError.new("Must be overriden by subclasses.")
  end

  # Adds a feedback items.
  #
  # @param item [Array] The items to add.
  def add_feedback_item(item)
    @feedback_items << item
  end

  # Prepares the feedback for output.
  def output_feedback
    if format == :xml then
      @output = Nokogiri::XML::Builder.new { |xml|
        xml.items do
          @feedback_items.each do |item|
            childs, attributes = split_output_item(item)

            xml.item(attributes) do
              childs.each { |name, value| xml.send(name, value) }
            end
          end
        end
      }.to_xml
    else
      @output = @feedback_items.to_yaml
    end
  end

  # Rounds a float to a certain precision.
  #
  # @param value [Float] The value to convert.
  # @param precision [Fixnum] The precision to use.
  # @return [Float] The rounded value.
  def round_float(value, precision = 3)
    factor = 10**precision
    (value * factor).round.to_f / factor
  end

  # Rounds a float to a certain precision and prints it as a string. Unneeded leading zero are remove.
  #
  # @param value [Float] The value to print.
  # @param precision [Fixnum] The precision to use.
  # @return [String] The formatted value.
  def format_float(value, precision = 3)
    value = round_float(value, precision)
    value = "%0.#{precision}f" % value
    value.gsub(/\.?0+$/, "")
  end

  protected
    # Instantiates the new class.
    #
    # @param file [String] The file name.
    def self.find_class(file)
      Pincerna.const_get(file.capitalize.gsub(/_(.)/) { $1.upcase})
    end

    # Executes filtering on matched data.
    #
    # @param matches [MatchData] The matched data.
    # @return [Array] The results of filtering.
    def execute_filtering(matches)
      # Get relevant matches and arguments.
      relevant = self.class::RELEVANT_MATCHES
      args = relevant.map {|key, value| value.call(self, matches[key]) }

      # Now perform the operation
      begin
        perform_filtering(*args)
      rescue => e
        raise e if debug_mode == :error
        []
      end
    end

    # Converts an array of key-value pairs to an hash.
    #
    # @param array [Array] The array to convert.
    # @return [Hash] The converted hash.
    def array_to_hash(array)
      array.reduce({}){ |rv, entry|
        rv[entry[0]] = entry[1]
        rv
      }
    end

    # Gets attributes and children for output.
    #
    # @param item [Hash] The output item.
    # @return [Array] An array with children and attributes.
    def split_output_item(item)
      item.partition {|k, _| [:title, :subtitle, :icon].include?(k) }.map {|a| array_to_hash(a) }
    end

    # Fetches remote JSON resource.
    #
    # @param url [String] The URL to get.
    # @param params [Hash] The parameters to pass to the server.
    # @param json [Boolean] If the response is a JSON object.
    # @return [Hash] The server's response.
    def fetch_remote_resource(url, params, json = true)
      args = {query: params}
      args[:head] = {"accept" => "application/json"} if json
      response = EM::HttpRequest.new(url, {connect_timeout: 5}).get(args).response
      json ? Oj.load(response) : response
    end

    # Executes a command and returns its output.
    #
    # @param args [Array] The command to execute, with its arguments.
    # @return [String] The output of the command
    def execute_command(*args)
      rv = ""
      IO.popen(args) {|f| rv = f.read }
      rv
    end

    # Returns the current debug mode.
    #
    # @return [Boolean|NilClass] The current debug mode.
    def debug_mode
      mode = ENV["PINCERNA_DEBUG"] || @debug
      !mode.nil? ? mode.to_sym : nil
    end
end

Class Method Details

+ (String) execute!(type, query, format = :xml, debug = nil)

Executes a filtering query.

Parameters:

  • type (Symbol)

    The type of the query.

  • query (String)

    The argument of the query.

  • format (String) (defaults to: :xml)

    The format to use. Valid values are :xml (default) and :yml.

  • debug (String) (defaults to: nil)

    The debug mode.

Returns:

  • (String)

    The result of the query.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/pincerna/base.rb', line 61

def self.execute!(type, query, format = :xml, debug = nil)
  instance = nil

  type = catch(:type) do
    TYPES.each do |file, matcher|
      throw(:type, file) if type =~ matcher
    end

    nil
  end

  if type
    instance = find_class(type).new(query, format, debug)
    instance.filter
  end

  instance
end

Instance Method Details

- (Object) add_feedback_item(item)

Adds a feedback items.

Parameters:

  • item (Array)

    The items to add.



138
139
140
# File 'lib/pincerna/base.rb', line 138

def add_feedback_item(item)
  @feedback_items << item
end

- (String) filter

Filters a query.

Returns:

  • (String)

    The feedback items of the query, formatted as XML.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/pincerna/base.rb', line 104

def filter
  # Match the query
  matches = self.class::MATCHER.match(@query)

  if matches then
    # Execute the filtering
    results = execute_filtering(matches)

    # Show results if appropriate
    process_results(results).each {|r| add_feedback_item(r) } if !results.empty?
  end

  output_feedback
end

- (String) format_float(value, precision = 3)

Rounds a float to a certain precision and prints it as a string. Unneeded leading zero are remove.

Parameters:

  • value (Float)

    The value to print.

  • precision (Fixnum) (defaults to: 3)

    The precision to use.

Returns:

  • (String)

    The formatted value.



176
177
178
179
180
# File 'lib/pincerna/base.rb', line 176

def format_float(value, precision = 3)
  value = round_float(value, precision)
  value = "%0.#{precision}f" % value
  value.gsub(/\.?0+$/, "")
end

- (Object) output_feedback

Prepares the feedback for output.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/pincerna/base.rb', line 143

def output_feedback
  if format == :xml then
    @output = Nokogiri::XML::Builder.new { |xml|
      xml.items do
        @feedback_items.each do |item|
          childs, attributes = split_output_item(item)

          xml.item(attributes) do
            childs.each { |name, value| xml.send(name, value) }
          end
        end
      end
    }.to_xml
  else
    @output = @feedback_items.to_yaml
  end
end

- (Array) perform_filtering(*args)

Filters a query.

Parameters:

  • args (Array)

    The arguments of the query.

Returns:

  • (Array)

    A list of items to process.

Raises:

  • (ArgumentError)


123
124
125
# File 'lib/pincerna/base.rb', line 123

def perform_filtering(*args)
  raise ArgumentError.new("Must be overriden by subclasses.")
end

- (Array) process_results(results)

Processes items to obtain feedback items.

Parameters:

  • results (Array)

    The items to process.

Returns:

  • (Array)

    The feedback items.

Raises:

  • (ArgumentError)


131
132
133
# File 'lib/pincerna/base.rb', line 131

def process_results(results)
  raise ArgumentError.new("Must be overriden by subclasses.")
end

- (Float) round_float(value, precision = 3)

Rounds a float to a certain precision.

Parameters:

  • value (Float)

    The value to convert.

  • precision (Fixnum) (defaults to: 3)

    The precision to use.

Returns:

  • (Float)

    The rounded value.



166
167
168
169
# File 'lib/pincerna/base.rb', line 166

def round_float(value, precision = 3)
  factor = 10**precision
  (value * factor).round.to_f / factor
end