foggy_bottom 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
6
+ test.rb
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in foggy_bottom.gemspec
4
+ gemspec
data/README ADDED
@@ -0,0 +1,4 @@
1
+ FoggyBottom is a Ruby wrapper around the FogBugz API. It's not a direct match, so you don't
2
+ need to know how to use the FogBugz API to use this library.
3
+
4
+ It's written in "normal" Ruby syntax and patterns, so no strange "port" of another library.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "foggy_bottom/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "foggy_bottom"
7
+ s.version = FoggyBottom::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Jon Moses"]
10
+ s.email = ["jon@burningbush.us"]
11
+ s.homepage = ""
12
+ s.summary = %q{Ruby API wrapper for FogBugz}
13
+ s.description = %q{It wraps the FogBugz API.}
14
+
15
+ #s.rubyforge_project = "foggy_bottom"
16
+ s.add_dependency('nokogiri')
17
+ s.add_dependency('activemodel')
18
+ s.add_dependency('hirb')
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+ end
@@ -0,0 +1,15 @@
1
+ require 'nokogiri'
2
+ require 'open-uri'
3
+ require 'digest/sha2'
4
+ require 'cgi'
5
+ require 'logger'
6
+ require 'delegate'
7
+
8
+ require 'active_support/core_ext/array'
9
+ require 'active_model'
10
+ require 'hirb'
11
+
12
+ require 'foggy_bottom/api'
13
+ require 'foggy_bottom/columns'
14
+ require 'foggy_bottom/case'
15
+ require 'foggy_bottom/console'
@@ -0,0 +1,76 @@
1
+ module FoggyBottom
2
+ class Api
3
+ attr_accessor :token, :url, :endpoint
4
+ attr_reader :logger
5
+
6
+ def initialize( url, username, password )
7
+ @logger = Logger.new(STDOUT).tap {|l| l.level = Logger::DEBUG }
8
+ @url = url[-1] == '/' ? url : "#{url}/"
9
+
10
+ token_filename = File.expand_path( "~/.foggy_bottom_token_" + Digest::SHA2.hexdigest("#{username}_#{password}") )
11
+
12
+ if File.exists?(token_filename)
13
+ load_details(token_filename)
14
+ else
15
+ fetch_endpoint
16
+ fetch_token(username, password, token_filename)
17
+ end
18
+ end
19
+
20
+ def exec(cmd, args = {})
21
+ get(build_url(cmd, args))
22
+ end
23
+
24
+ def find( issue_id )
25
+ Case.find(issue_id, self)
26
+ end
27
+ alias :[] :find
28
+
29
+ def search( terms )
30
+ Case.search(terms, self)
31
+ end
32
+
33
+ protected
34
+ def fetch_token(username, password, token_filename)
35
+ @token ||= begin
36
+ logger.debug("Fetching token")
37
+ get(build_url(:logon, :email => username, :password => password)).at_css("response token").content.tap do |token|
38
+ logger.debug(" done.")
39
+ save_token(token, token_filename)
40
+ end
41
+ end
42
+ end
43
+
44
+ def load_details(token_filename)
45
+ logger.debug("Loading cached credentials")
46
+
47
+ @endpoint, @token = File.read(token_filename).split("\n").map(&:strip)
48
+ end
49
+
50
+ def save_token(token, token_filename)
51
+ logger.debug("Caching token")
52
+
53
+ File.open(token_filename, 'w') do |out|
54
+ out << endpoint
55
+ out << "\n"
56
+ out << token
57
+ end
58
+ end
59
+
60
+ def fetch_endpoint
61
+ @endpoint ||= begin
62
+ logger.debug("Fetching endpoint")
63
+ Nokogiri.XML( open("#{url}/api.xml") {|f| f.read } ).at_css("response url").content
64
+ end
65
+ end
66
+
67
+ def get(path)
68
+ logger.debug("Attempting to access: #{path}")
69
+ Nokogiri.XML( open(path) {|f| f.read } )
70
+ end
71
+
72
+ def build_url(cmd, args)
73
+ url + endpoint + args.merge(:cmd => cmd.to_s, :token => token).inject([]) {|memo, (key, value)| memo << "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,83 @@
1
+ class FoggyBottom::Case
2
+ include FoggyBottom::Columns::Case
3
+
4
+ include ActiveModel::AttributeMethods
5
+ include ActiveModel::Dirty
6
+ include ActiveModel::Serialization
7
+
8
+ define_attribute_methods ALL_COLUMNS
9
+
10
+ attr_accessor :api
11
+
12
+ delegate :logger, :to => :api
13
+
14
+ class << self
15
+ def find( case_id, api )
16
+ details = api.exec(:search, :q => case_id, :cols => FoggyBottom::Columns::Case::ALL_COLUMNS.join(',') ).at_css("case")
17
+
18
+ create_from_xml(details, api) if details
19
+ end
20
+
21
+ def search( terms, api )
22
+ api.exec(:search, :q => terms, :cols => FoggyBottom::Columns::Case::ALL_COLUMNS.join(',')).css('case').collect do |details|
23
+ create_from_xml(details, api)
24
+ end
25
+ end
26
+
27
+ def create_from_xml(xml, api)
28
+ new( {}.tap do |attributes|
29
+ (FoggyBottom::Columns::Case::ALL_COLUMNS - %w(tags)).each do |col|
30
+ attributes[col] = xml.at_css(col).content
31
+ end
32
+
33
+ attributes['tags'] = [].tap do |tags|
34
+ xml.css("tag").each {|t| tags << t.content }
35
+ end
36
+ end).tap do |instance|
37
+ instance.api = api
38
+ end
39
+
40
+ end
41
+ end
42
+
43
+ def initialize(attributes = {})
44
+ @attributes = attributes.stringify_keys
45
+ end
46
+
47
+ def attributes=(new_attributes)
48
+ new_attributes.each_pair {|k,v| send("#{k}=", v) }
49
+ end
50
+
51
+ def save( comment = nil )
52
+ return unless changed?
53
+
54
+ save!(comment)
55
+ end
56
+
57
+ def resolve( comment = nil )
58
+ save!(comment, :resolve)
59
+ end
60
+
61
+ def to_s
62
+ "#{ixBug} - #{sTitle}"
63
+ end
64
+
65
+ protected
66
+ def attributes
67
+ @attributes
68
+ end
69
+
70
+ def save!(comment, action = :edit)
71
+ @previously_changed = changes
72
+
73
+ logger.debug("Saving changes for #{ixBug} columns #{changed.join(', ')} with action #{action}")
74
+ api.exec(action, parameters('sEvent' => comment))
75
+ logger.debug(" done")
76
+
77
+ @changed_attributes.clear
78
+ end
79
+
80
+ def parameters( extra = {} )
81
+ changed.inject({}) {|memo, key| memo.merge(key => send(key))}.merge(extra).merge('ixBug' => ixBug)
82
+ end
83
+ end
@@ -0,0 +1,80 @@
1
+ module FoggyBottom
2
+ module Columns
3
+ module Case
4
+ def self.included(base)
5
+ base.class_eval do
6
+ ALL_COLUMNS.each do |column|
7
+ define_method(column) do
8
+ @attributes[column]
9
+ end
10
+
11
+ define_method("#{column}=") do |arg|
12
+ send("#{column}_will_change!") unless send(column) == arg
13
+
14
+ @attributes[column] = arg
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ ALL_COLUMNS = %w(
21
+ ixBug
22
+ ixBugParent
23
+ ixBugChildren
24
+ tags
25
+ fOpen
26
+ sTitle
27
+ sOriginalTitle
28
+ sLatestTextSummary
29
+ ixBugEventLatestText
30
+ ixProject
31
+ sProject
32
+ ixArea
33
+ sArea
34
+ ixGroup
35
+ ixPersonAssignedTo
36
+ sPersonAssignedTo
37
+ sEmailAssignedTo
38
+ ixPersonOpenedBy
39
+ ixPersonResolvedBy
40
+ ixPersonClosedBy
41
+ ixPersonLastEditedBy
42
+ ixStatus
43
+ sStatus
44
+ ixPriority
45
+ sPriority
46
+ ixFixFor
47
+ sFixFor
48
+ dtFixFor
49
+ sVersion
50
+ sComputer
51
+ hrsOrigEst
52
+ hrsCurrEst
53
+ hrsElapsed
54
+ c
55
+ sCustomerEmail
56
+ ixMailbox
57
+ ixCategory
58
+ sCategory
59
+ dtOpened
60
+ dtResolved
61
+ dtClosed
62
+ ixBugEventLatest
63
+ dtLastUpdated
64
+ fReplied
65
+ fForwarded
66
+ sTicket
67
+ ixDiscussTopic
68
+ dtDue
69
+ sReleaseNotes
70
+ ixBugEventLastView
71
+ dtLastView
72
+ ixRelatedBugs
73
+ sScoutDescription
74
+ sScoutMessage
75
+ fScoutStopReporting
76
+ fSubscribed
77
+ )
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,59 @@
1
+ class FoggyBottom::Console
2
+ delegate :logger, :to => :api
3
+
4
+ attr_accessor :api
5
+ def initialize(api)
6
+ @api = api
7
+ end
8
+
9
+ def run
10
+ while( cmd = show_menu ) do
11
+ case cmd
12
+ when :search
13
+ search(get("Terms"))
14
+ when :resolve
15
+ resolve(get("Case ID"))
16
+ when :quit
17
+ exit 0
18
+ else
19
+ puts " Unknown choice"
20
+ end
21
+ end
22
+ end
23
+
24
+ protected
25
+ def show_menu
26
+ commands = %w( search resolve quit )
27
+ choice = (Hirb::Menu.render commands, :helper_class => false, :directions => false).first
28
+ choice ? choice.to_sym : :unknown
29
+ end
30
+
31
+ def choose( arguments, options = {})
32
+ Hirb::Menu.render arguments, options.merge(:helper_class => false)
33
+ end
34
+
35
+ def get(prompt)
36
+ STDOUT.write "#{prompt}: "
37
+ STDOUT.flush
38
+ STDIN.gets.chomp
39
+ end
40
+
41
+ def search(terms)
42
+ unless terms
43
+ puts "Search terms are required."
44
+ return
45
+ end
46
+
47
+ cases = api.search(terms)
48
+ choose(cases.map(&:to_s))
49
+ end
50
+
51
+ def resolve( case_id )
52
+ bug = api.find(case_id)
53
+ if bug
54
+ bug.resolve
55
+ else
56
+ puts "Bug #{case_id} not found"
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ module FoggyBottom
2
+ VERSION = "0.0.2"
3
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: foggy_bottom
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jon Moses
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-03 00:00:00.000000000 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: nokogiri
17
+ requirement: &2152537960 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2152537960
26
+ - !ruby/object:Gem::Dependency
27
+ name: activemodel
28
+ requirement: &2152537540 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *2152537540
37
+ - !ruby/object:Gem::Dependency
38
+ name: hirb
39
+ requirement: &2152537120 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *2152537120
48
+ description: It wraps the FogBugz API.
49
+ email:
50
+ - jon@burningbush.us
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - .gitignore
56
+ - Gemfile
57
+ - README
58
+ - Rakefile
59
+ - foggy_bottom.gemspec
60
+ - lib/foggy_bottom.rb
61
+ - lib/foggy_bottom/api.rb
62
+ - lib/foggy_bottom/case.rb
63
+ - lib/foggy_bottom/columns.rb
64
+ - lib/foggy_bottom/console.rb
65
+ - lib/foggy_bottom/version.rb
66
+ has_rdoc: true
67
+ homepage: ''
68
+ licenses: []
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 1.6.2
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Ruby API wrapper for FogBugz
91
+ test_files: []