foggy_bottom 0.0.2
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.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/README +4 -0
- data/Rakefile +2 -0
- data/foggy_bottom.gemspec +24 -0
- data/lib/foggy_bottom.rb +15 -0
- data/lib/foggy_bottom/api.rb +76 -0
- data/lib/foggy_bottom/case.rb +83 -0
- data/lib/foggy_bottom/columns.rb +80 -0
- data/lib/foggy_bottom/console.rb +59 -0
- data/lib/foggy_bottom/version.rb +3 -0
- metadata +91 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README
ADDED
data/Rakefile
ADDED
@@ -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
|
data/lib/foggy_bottom.rb
ADDED
@@ -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
|
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: []
|