fiddler 0.0.1 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/README.md +70 -0
  2. data/lib/fiddler.rb +5 -1
  3. data/lib/fiddler/attachment.rb +142 -0
  4. data/lib/fiddler/configuration.rb +7 -2
  5. data/lib/fiddler/connection_manager.rb +62 -20
  6. data/lib/fiddler/errors.rb +1 -0
  7. data/lib/fiddler/extensions.rb +2 -0
  8. data/lib/fiddler/extensions/file.rb +5 -0
  9. data/lib/fiddler/extensions/hash.rb +15 -0
  10. data/lib/fiddler/formatters/base_formatter.rb +3 -0
  11. data/lib/fiddler/formatters/search_request_formatter.rb +2 -2
  12. data/lib/fiddler/history.rb +32 -0
  13. data/lib/fiddler/parsers.rb +3 -1
  14. data/lib/fiddler/parsers/attachment_parser.rb +54 -0
  15. data/lib/fiddler/parsers/base_parser.rb +41 -3
  16. data/lib/fiddler/parsers/history_parser.rb +72 -0
  17. data/lib/fiddler/parsers/ticket_parser.rb +52 -21
  18. data/lib/fiddler/ticket.rb +141 -43
  19. data/lib/fiddler/version.rb +1 -1
  20. data/spec/attachment_spec.rb +26 -0
  21. data/spec/cassettes/change-ownership-take.yml +98 -0
  22. data/spec/cassettes/get-tickets.yml +89087 -0
  23. data/spec/cassettes/reply-to-tickets.yml +232 -0
  24. data/spec/cassettes/root-request.yml +79 -0
  25. data/spec/cassettes/search-tickets.yml +38249 -0
  26. data/spec/cassettes/ticket-histories-count.yml +2195 -0
  27. data/spec/cassettes/ticket-histories.yml +954 -0
  28. data/spec/connection_manager_spec.rb +5 -1
  29. data/spec/formatters/search_request_formatter_spec.rb +13 -0
  30. data/spec/parsers/attachment_parser_spec.rb +29 -0
  31. data/spec/parsers/base_parser_spec.rb +16 -0
  32. data/spec/parsers/history_parser_spec.rb +32 -0
  33. data/spec/parsers/ticket_parser_spec.rb +5 -1
  34. data/spec/spec_helper.rb +9 -0
  35. data/spec/ticket_spec.rb +188 -6
  36. metadata +80 -9
@@ -1,2 +1,4 @@
1
1
  require 'fiddler/parsers/base_parser'
2
- require 'fiddler/parsers/ticket_parser'
2
+ require 'fiddler/parsers/ticket_parser'
3
+ require 'fiddler/parsers/history_parser'
4
+ require 'fiddler/parsers/attachment_parser'
@@ -0,0 +1,54 @@
1
+ module Fiddler
2
+ module Parsers
3
+ class AttachmentParser < BaseParser
4
+ def self.parse_single(response)
5
+ response = check_response_code(response)
6
+ response = check_for_errors(response)
7
+ attachment_from_response(response)
8
+ end
9
+
10
+ def self.parse_content(response)
11
+ response = check_response_code(response,false,false)
12
+ return response.join("\n")
13
+ end
14
+
15
+ protected
16
+
17
+ def self.check_for_errors(response)
18
+ if response.length == 1
19
+ raise Fiddler::FiddlerError, response.first
20
+ end
21
+ response
22
+ end
23
+
24
+ def self.attachment_from_response(response)
25
+ result = {}
26
+ collect_headers = false
27
+ headers = []
28
+ response.each do |line|
29
+ matches = /^(\S*?):\s(.*)/.match(line)
30
+ if(matches)
31
+ key = matches[1].underscore
32
+ result[key] = matches[2]
33
+
34
+ if key == "headers"
35
+ collect_headers = true
36
+ next
37
+ elsif key == "content"
38
+ break
39
+ end
40
+ end
41
+
42
+ spaced_content_matches = /^\s{9}(.*)$/.match(line)
43
+ if spaced_content_matches and collect_headers
44
+ headers << spaced_content_matches[1]
45
+ end
46
+ end
47
+ result.delete("content")
48
+ headers.unshift(result["headers"])
49
+ result["headers"] = headers
50
+ attachment = Fiddler::Attachment.new(result)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -5,19 +5,57 @@ module Fiddler
5
5
  SUCCESS_CODES = (200..299).to_a
6
6
  ERROR_CODES = (400..499).to_a
7
7
 
8
- def self.check_response_code(response)
9
- lines = response.split("\n").reject { |l| l.nil? or l == "" }
8
+ def self.check_response_code(response, reject_blank_lines=true, raise_error_for_empty_response=true)
9
+ response = safe_encode(response)
10
+ lines = response.split("\n")
11
+ lines = lines.delete_if { |l| l.nil? or l == "" } if reject_blank_lines
10
12
  if lines.count == 0
11
- raise RequestError, "Empty Response"
13
+ if raise_error_for_empty_response
14
+ raise RequestError, "Empty Response"
15
+ else
16
+ return []
17
+ end
12
18
  else
13
19
  status_line = lines.shift
14
20
  version, status_code, status_text = status_line.split(/\s+/,2)
15
21
  unless SUCCESS_CODES.include?(status_code.to_i)
16
22
  raise RequestError, status_text
17
23
  end
24
+ lines.shift unless reject_blank_lines
18
25
  lines
19
26
  end
20
27
  end
28
+
29
+ def self.tokenize_response(response)
30
+ tokens = Array.new
31
+ current = Array.new
32
+ response.each do |item|
33
+ if(item == "--")
34
+ tokens << current
35
+ current = Array.new
36
+ next
37
+ end
38
+ current << item
39
+ end
40
+ tokens << current unless current.empty?
41
+ tokens
42
+ end
43
+
44
+ protected
45
+
46
+ def self.safe_encode(response)
47
+ require 'iconv' unless String.method_defined?(:encode)
48
+ if String.method_defined?(:encode)
49
+ response.encode!('UTF-8', 'UTF-8', :invalid => :replace)
50
+ else
51
+ begin
52
+ Iconv.iconv("UTF-8//IGNORE", "UTF-8", response).join("")
53
+ rescue Exception => e
54
+ raise IllegalCharacterError, "Attachment contains illegal characters"
55
+ end
56
+ end
57
+ response
58
+ end
21
59
  end
22
60
  end
23
61
  end
@@ -0,0 +1,72 @@
1
+ module Fiddler
2
+ module Parsers
3
+ class HistoryParser < BaseParser
4
+ def self.parse_single(response)
5
+ response = check_response_code(response)
6
+ response = check_for_errors(response)
7
+ history_from_response(response)
8
+ end
9
+
10
+ def self.parse_multiple(response)
11
+ response = check_response_code(response)
12
+ response = check_for_errors(response)
13
+ token_responses = tokenize_response(response)
14
+ histories = Array.new
15
+ token_responses.each do |token_response|
16
+ histories << history_from_response(token_response)
17
+ end
18
+ histories
19
+ end
20
+
21
+ protected
22
+
23
+ def self.check_for_errors(response)
24
+ if response.length == 1
25
+ raise Fiddler::FiddlerError, response.first
26
+ else
27
+ response.shift
28
+ end
29
+ response
30
+ end
31
+
32
+ def self.history_from_response(response)
33
+ result = {}
34
+ content_lines = []
35
+ result["attachment_ids"] = []
36
+ collect_content = false
37
+ collect_attachments = false
38
+ response.each do |line|
39
+ matches = /^(\S*?):\s(.*)/.match(line)
40
+ if(matches)
41
+ key = matches[1].underscore
42
+ result[key] = matches[2]
43
+ if key == "content"
44
+ collect_content = true
45
+ elsif key == "attachments"
46
+ collect_content = false
47
+ collect_attachments = true
48
+ end
49
+ end
50
+
51
+ if collect_content
52
+ content_matches = /^\s{9}(.*)$/.match(line)
53
+ if(content_matches)
54
+ content_lines << content_matches[1]
55
+ end
56
+ end
57
+
58
+ if collect_attachments
59
+ attachment_matches = /^\s{13}(.*?):\s(.*)/.match(line)
60
+ if(attachment_matches)
61
+ result["attachment_ids"] << attachment_matches[1]
62
+ end
63
+ end
64
+ end
65
+ result["attachment_ids"].count.times { content_lines.pop }
66
+ result["content"] += content_lines.join("\n")
67
+ result.delete("attachments")
68
+ history = Fiddler::History.new(result)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -10,12 +10,39 @@ module Fiddler
10
10
  def self.parse_multiple(response)
11
11
  response = check_response_code(response)
12
12
  response = check_for_errors(response)
13
- ticket_token_responses = tokenize_response(response)
14
- tickets = Array.new
15
- ticket_token_responses.each do |token_response|
16
- tickets << ticket_from_response(token_response)
13
+ if response.count == 0
14
+ []
15
+ else
16
+ ticket_token_responses = tokenize_response(response)
17
+ tickets = Array.new
18
+ ticket_token_responses.each do |token_response|
19
+ tickets << ticket_from_response(token_response)
20
+ end
21
+ tickets
22
+ end
23
+ end
24
+
25
+ def self.parse_reply_response(response)
26
+ response = check_response_code(response)
27
+ return !response.first.match(/^# Message recorded/).nil?
28
+ end
29
+
30
+ def self.parse_change_ownership_response(response)
31
+ response = check_response_code(response)
32
+ if response.first =~ /^# Owner changed from (\S+) to (\S+)/
33
+ return $2
34
+ else
35
+ return nil
36
+ end
37
+ end
38
+
39
+ def self.parse_update_response(response, method)
40
+ response = check_response_code(response)
41
+ if method == :create
42
+ return response.first =~ /^# Ticket (\S+) created/ ? $1 : nil
43
+ elsif method == :update
44
+ return response.first =~ /^# Ticket (\S+) updated/ ? $1 : nil
17
45
  end
18
- tickets
19
46
  end
20
47
 
21
48
  protected
@@ -24,35 +51,39 @@ module Fiddler
24
51
  message = response.first.strip
25
52
  if message =~ /^#/
26
53
  raise Fiddler::TicketNotFoundError, message
54
+ elsif message == "No matching results."
55
+ response = []
27
56
  end
28
57
  response
29
58
  end
30
59
 
31
60
  def self.ticket_from_response(response)
32
61
  result = {}
62
+ prev_key = nil
33
63
  response.each do |line|
34
- matches = /^(.*?):\s(.*)/.match(line)
64
+ matches = /^(\S*?):\s(.*)/.match(line)
35
65
  if(matches)
36
66
  key = matches[1].underscore
67
+ prev_key = key
37
68
  result[key] = matches[2]
69
+ else
70
+ whitespace_matches = /^\s{12}(.*)/.match(line)
71
+ if whitespace_matches and prev_key
72
+ values = result[prev_key]
73
+ values = values.split(",") unless values.is_a?(Array)
74
+ result[prev_key] = values.concat(whitespace_matches[1].split(",")).collect { |x| x.strip }
75
+ end
38
76
  end
39
77
  end
40
- Fiddler::Ticket.new(result)
41
- end
42
-
43
- def self.tokenize_response(response)
44
- tokens = Array.new
45
- current = Array.new
46
- response.each do |item|
47
- if(item == "--")
48
- tokens << current
49
- current = Array.new
50
- next
51
- end
52
- current << item
78
+ begin
79
+ id = result['id'].scan(/^ticket\/(\d*)$/).first.first.to_i
80
+ rescue
81
+ raise RequestError, "Unexpected response for id : #{result['id']}"
53
82
  end
54
- tokens
83
+ ticket = Fiddler::Ticket.new(result)
84
+ ticket.id = id
85
+ return ticket
55
86
  end
56
87
  end
57
88
  end
58
- end
89
+ end
@@ -1,42 +1,147 @@
1
+ require 'active_attr'
1
2
  module Fiddler
2
3
  class Ticket
4
+ include ActiveAttr::Model
3
5
 
4
- DefaultAttributes = %w(queue owner creator subject status priority initial_priority final_priority requestors cc admin_cc created starts started due resolved told last_updated time_estimated time_worked time_left text).inject({}){|memo, k| memo[k] = nil; memo}
5
- RequiredAttributes = %w(queue subject)
6
+ attribute :id, :default => 'ticket/new'
7
+ attribute :queue
8
+ attribute :owner
9
+ attribute :creator
10
+ attribute :subject
11
+ attribute :status
12
+ attribute :priority
13
+ attribute :initial_priority
14
+ attribute :final_priority
15
+ attribute :requestors
16
+ attribute :cc
17
+ attribute :admin_cc
18
+ attribute :created
19
+ attribute :starts
20
+ attribute :started
21
+ attribute :due
22
+ attribute :resolved
23
+ attribute :told
24
+ attribute :last_updated
25
+ attribute :time_estimated
26
+ attribute :time_worked
27
+ attribute :time_left
28
+ attribute :text
6
29
 
7
- attr_reader :histories, :saved
8
-
9
- # Initializes a new instance of ticket object
30
+ validates_presence_of :id, :queue, :subject
31
+
32
+ def histories
33
+ if @histories == nil
34
+ url = "ticket/#{id}/history"
35
+ response = Fiddler::ConnectionManager.get(url, {:format => "l"})
36
+ @histories = Fiddler::Parsers::HistoryParser.parse_multiple(response)
37
+ end
38
+ @histories
39
+ end
40
+
41
+ # make the setting and getting of requestors easier
42
+ def requestor_array
43
+ self.requestors.split(",").collect { |x| x.strip }
44
+ end
45
+
46
+ def requestor_array=(requestor_array)
47
+ if requestor_array.is_a?(Array)
48
+ self.requestors = requestor_array.join(", ")
49
+ end
50
+ end
51
+
52
+ # Add a comment to a ticket
53
+ # Example:
54
+ # tix = Ticket.get(1000)
55
+ # tix.comment("This is a comment", :time_worked => 45, :cc => 'someone@example.com')
56
+ #
57
+ # Attachments
58
+ # tix.comment("This is a comment", :attachments => "/tmp/filename.txt")
59
+ #
60
+ # Attachment as a file descriptor
61
+ # tix.correspond("This is a comment", :attachments => File.new("/tmp/filename.txt"))
10
62
  #
11
- # @params [Hash] of the initial options
12
- def initialize(attributes={})
13
- if attributes
14
- @attributes = DefaultAttributes.merge(attributes)
63
+ # Attachment as a ActionDispatch::Http::UploadedFile instance
64
+ # tix.comment("This is a comment", :attachments => [params[:attachment_1], params[:attachment_2]])
65
+ #
66
+ def comment(comment, opt = {})
67
+ reply('Comment', comment, opt)
68
+ end
69
+
70
+ def correspond(comment, opt = {})
71
+ reply('Correspond', comment, opt)
72
+ end
73
+
74
+ def steal
75
+ change_ownership "Steal"
76
+ end
77
+
78
+ def take
79
+ change_ownership "Take"
80
+ end
81
+
82
+ def untake
83
+ change_ownership "Untake"
84
+ end
85
+
86
+ def save
87
+ id == "ticket/new" ? create : update
88
+ end
89
+
90
+ protected
91
+
92
+ def reply(method, comment, opt)
93
+ # make sure the options are only the valid ones.
94
+ valid_options = [:cc, :bcc, :time_worked, :attachments, :status]
95
+ opt.delete_if { |key,value| !valid_options.include?(key) }
96
+
97
+ payload = { :text => comment, :action => method}.merge(opt)
98
+ payload.delete(:attachments)
99
+
100
+ attachments = Fiddler::AttachmentCollection.fill(opt[:attachments])
101
+ payload.merge!(:attachment => attachments.map(&:name).join(",")) unless attachments.empty?
102
+
103
+ response = Fiddler::ConnectionManager.post_content("ticket/#{id}/comment", { :content => payload.to_content_format }.merge(attachments.to_payload) )
104
+
105
+ result = Fiddler::Parsers::TicketParser.parse_reply_response(response)
106
+
107
+ # update the ticket parameters if the action was successful
108
+ if result and opt.has_key?(:status)
109
+ self.status = opt[:status]
110
+ result
15
111
  else
16
- @attributes = DefaultAttributes
112
+ result
17
113
  end
18
- @attributes.update(:id => 'ticket/new')
19
- @saved = false
20
- @histories = []
21
- @new_record = true
22
- add_methods!
23
114
  end
24
115
 
25
- def add_methods!
26
- @attributes.each do |key, value|
27
- (class << self; self; end).send :define_method, key do
28
- return @attributes[key]
29
- end
30
- (class << self; self; end).send :define_method, "#{key}=" do |new_val|
31
- @attributes[key] = new_val
32
- end
116
+ def change_ownership(method)
117
+ payload = { "Action" => method }
118
+ response = Fiddler::ConnectionManager.post("ticket/#{id}/take", :content => payload.to_content_format)
119
+ new_owner = Fiddler::Parsers::TicketParser.parse_change_ownership_response(response)
120
+ if new_owner
121
+ self.owner = new_owner
122
+ return self
123
+ else
124
+ return nil
33
125
  end
34
126
  end
35
127
 
36
- def description
37
- @attributes.each do |key,value|
38
- puts "#{key} = #{value}"
39
- end
128
+ def create
129
+ return false unless valid?
130
+ response = Fiddler::ConnectionManager.post("ticket/new", :content => @attributes.to_content_format)
131
+ id = Fiddler::Parsers::TicketParser.parse_update_response(response, :create)
132
+ return false unless id
133
+ self.id = id.to_i
134
+ true
135
+ end
136
+
137
+ def update
138
+ return false unless valid?
139
+ payload = @attributes.clone
140
+ payload.delete("text")
141
+ payload.delete("id")
142
+ response = Fiddler::ConnectionManager.post("ticket/#{id}/edit", :content => payload.to_content_format)
143
+ id = Fiddler::Parsers::TicketParser.parse_update_response(response, :update)
144
+ return !id.nil?
40
145
  end
41
146
 
42
147
  # Class methods
@@ -49,6 +154,7 @@ module Fiddler
49
154
  url = "ticket/#{id}"
50
155
  response = Fiddler::ConnectionManager.get(url)
51
156
  ticket = Fiddler::Parsers::TicketParser.parse_single(response)
157
+ ticket
52
158
  end
53
159
 
54
160
  # Search the tickets with the given conditions
@@ -56,9 +162,14 @@ module Fiddler
56
162
  # @params [Hash] of conditions
57
163
  # @returns [Array<Ticket>] of the tickets matching the criteria
58
164
  def all(conditions={})
165
+ tickets = []
59
166
  url = "search/ticket"
60
- response = Fiddler::ConnectionManager.get(url,Fiddler::Formatters::SearchRequestFormatter.format(conditions))
61
- ticket = Fiddler::Parsers::TicketParser.parse_multiple(response)
167
+ request_hash = Fiddler::Formatters::SearchRequestFormatter.format(conditions)
168
+ unless request_hash.empty?
169
+ response = Fiddler::ConnectionManager.get(url,request_hash)
170
+ tickets = Fiddler::Parsers::TicketParser.parse_multiple(response)
171
+ end
172
+ tickets
62
173
  end
63
174
 
64
175
  # Creates a new ticket with the given options, it will not save the ticket
@@ -67,19 +178,6 @@ module Fiddler
67
178
  # @returns [Ticket] returns new ticket object
68
179
  def create(options)
69
180
  end
70
-
71
- # Saves the changes to the ticket object
72
- #
73
- # @returns [boolean] save succcessful or not
74
- def save
75
- end
76
-
77
- # Find the tickets with the given options
78
- #
79
- # @params [Hash] of options
80
- # @returns [Array<Ticket>] of ticket matching the criteria
81
- def find(options={})
82
- end
83
181
  end
84
182
  end
85
- end
183
+ end