fiddler 0.0.1 → 1.0.1

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 (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