davetron5000-gliffy 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +52 -0
- data/bin/gliffy +8 -0
- data/ext/array_has_response.rb +10 -0
- data/lib/gliffy.rb +1 -0
- data/lib/gliffy/account.rb +55 -0
- data/lib/gliffy/cli.rb +260 -0
- data/lib/gliffy/commands/commands.rb +149 -0
- data/lib/gliffy/config.rb +55 -0
- data/lib/gliffy/diagram.rb +100 -0
- data/lib/gliffy/folder.rb +63 -0
- data/lib/gliffy/gliffy.rb +367 -0
- data/lib/gliffy/response.rb +127 -0
- data/lib/gliffy/rest.rb +110 -0
- data/lib/gliffy/url.rb +67 -0
- data/lib/gliffy/user.rb +72 -0
- metadata +81 -0
data/README.rdoc
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
<em>The Gliffy API is currently in private beta. This will be a Ruby implementation to access your Gliffy account.</em>
|
2
|
+
|
3
|
+
This consists of a Ruby client for Gliffy as well as a simple command line tool to access your Gliffy account using that client.
|
4
|
+
|
5
|
+
== Client Library
|
6
|
+
|
7
|
+
Main entry point is Gliffy::Handle, though you should look at Gliffy::Config for configuration options. Here's an example of getting the list of diagrams and then downloading the first one as a JPEG
|
8
|
+
|
9
|
+
require 'gliffy'
|
10
|
+
|
11
|
+
Gliffy::Config.config.api_key='aa9d0e8d-449b-44ad-904a-bf87deb7c403'
|
12
|
+
Gliffy::Config.config.secret_key='a52585f7-5952-4229-8bbe-b5787521b571'
|
13
|
+
Gliffy::Config.config.account_name='Initech'
|
14
|
+
|
15
|
+
handle = Gliffy::Handle.new('lumberg@initech.com')
|
16
|
+
|
17
|
+
diagrams = handle.get_diagrams
|
18
|
+
diagram_name_safe = diagrams[0].name.gsub(/ /,'_')
|
19
|
+
handle.get_diagram_as_image(diagrams[0].id,:mime_type => :jpeg,:file=>"#{diagram_name_safe}.jpg")
|
20
|
+
|
21
|
+
The Gliffy::Handle is a <b>user-session based connection</b> to Gliffy. You shoudl therefore engineer your application to keep one of these per user who will access Gliffy and *not* make a global instance.
|
22
|
+
|
23
|
+
* RDoc is available a http://davetron5000.github.com/gliffy
|
24
|
+
* Source is available at http://github.com/davetron5000/gliffy/tree/master
|
25
|
+
|
26
|
+
== Command Line Client
|
27
|
+
|
28
|
+
+gliffy+ +help+ shows a list of commands. Currently, there are:
|
29
|
+
|
30
|
+
[+delete+] Delete a diagram (also: del,rm)
|
31
|
+
[+edit+] Edit a diagram
|
32
|
+
[+get+] Download a diagram as an image to a file
|
33
|
+
[+help+] Show commands
|
34
|
+
[+list+] List all diagrams in the account (also: ls)
|
35
|
+
[+new+] Create a new diagram
|
36
|
+
[+url+] Get the URL for an image
|
37
|
+
|
38
|
+
=== Configuration
|
39
|
+
|
40
|
+
The first time you run the client, it will create a base configuration in your home directory called <tt>.gliffyrc</tt>, and will ask you to provide it with four pieces of information:
|
41
|
+
|
42
|
+
[API Key] This is the API Key given to you by Gliffy
|
43
|
+
[Secret Key] This is the Secret Key given to you by Gliffy. <b>Do not check this value into source control</b>
|
44
|
+
[Account Name] Your account's name. This should've been provided with your API Key
|
45
|
+
[Username] The username to work under. The command line client only allows single-user access to your Gliffy account. If you just signed up for Gliffy, this is probably the email address you used to sign up
|
46
|
+
|
47
|
+
If you are not on OS X, you will want to configure two other options, and you can do this by editing your <tt>.gliffyrc</tt> directly:
|
48
|
+
|
49
|
+
[<tt>open_image</tt>] Configures the command for opening an image from the command line. This should be a string of the form <tt>command %s</tt> where <tt>%s</tt> will be replaced with the name of the image file to open.
|
50
|
+
[<tt>open_url</tt>] Configures the command for opening an url from the command line. This should be a string of the form <tt>command %s</tt> where <tt>%s</tt> will be replaced with the name of the url to open.
|
51
|
+
|
52
|
+
The other options are all documented in Gliffy::Config. Note that your most previously received user token is stored here as well, to keep from getting it every time the client is run.
|
data/bin/gliffy
ADDED
data/lib/gliffy.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'gliffy/gliffy'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'array_has_response'
|
3
|
+
require 'gliffy/rest'
|
4
|
+
|
5
|
+
include REXML
|
6
|
+
|
7
|
+
module Gliffy
|
8
|
+
|
9
|
+
# Represents on account
|
10
|
+
class Account < Response
|
11
|
+
|
12
|
+
attr_reader :name
|
13
|
+
attr_reader :id
|
14
|
+
# Either :basic or :premium
|
15
|
+
attr_reader :type
|
16
|
+
attr_reader :max_users
|
17
|
+
# A Time representing the date on which this account expires
|
18
|
+
attr_reader :expiration_date
|
19
|
+
|
20
|
+
attr_reader :users
|
21
|
+
|
22
|
+
def self.from_xml(element)
|
23
|
+
id = element.attributes['id'].to_i
|
24
|
+
type = element.attributes['account-type']
|
25
|
+
if type == 'Basic'
|
26
|
+
type = :basic
|
27
|
+
elsif type == 'Premium'
|
28
|
+
type = :premium
|
29
|
+
else
|
30
|
+
raise "Unknown type #{type}"
|
31
|
+
end
|
32
|
+
max_users = element.attributes['max-users'].to_i
|
33
|
+
expiration_date = Time.at(element.elements['expiration-date'].text.to_i / 1000)
|
34
|
+
name = element.elements['name'].text
|
35
|
+
users = Users.from_xml(element.elements['users'])
|
36
|
+
|
37
|
+
Account.new(id,type,name,max_users,expiration_date,users)
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def initialize(id,type,name,max_users,expiration_date,users=nil)
|
43
|
+
super()
|
44
|
+
@id = id
|
45
|
+
@type = type
|
46
|
+
@name = name
|
47
|
+
@max_users = max_users
|
48
|
+
@expiration_date = expiration_date
|
49
|
+
@users = users
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
class Accounts < ArrayResponseParser; end
|
55
|
+
end
|
data/lib/gliffy/cli.rb
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
require 'fileutils'
|
3
|
+
require 'yaml'
|
4
|
+
require 'gliffy'
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
module Gliffy
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# A command line option to the gliffy command line client
|
11
|
+
class Command
|
12
|
+
|
13
|
+
# Global flags
|
14
|
+
GLOBAL_FLAGS = {
|
15
|
+
'-v' => 'be more verbose (overrides config)',
|
16
|
+
}
|
17
|
+
|
18
|
+
# Global access to all configured commands
|
19
|
+
def self.commands
|
20
|
+
@@commands ||= {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.aliases
|
24
|
+
@@aliases ||= {}
|
25
|
+
end
|
26
|
+
|
27
|
+
# The name of the command
|
28
|
+
attr_reader :name
|
29
|
+
# a short description
|
30
|
+
attr_reader :description
|
31
|
+
# a usage statement
|
32
|
+
attr_reader :usage
|
33
|
+
|
34
|
+
# Create a new command
|
35
|
+
#
|
36
|
+
# [+name+] the name of the command (should be short, no spaces)
|
37
|
+
# [+description+] short description of the command
|
38
|
+
# [+offline+] true if this command doesn't require a connection to Gliffy
|
39
|
+
# [+usage+] usage statement
|
40
|
+
# [+block+] A block that represents the command itself. This block will take the gliffy handle and the args array as arguments *or* just the arguments if it is an "offline"
|
41
|
+
# command
|
42
|
+
def initialize(name,description,offline,usage,block)
|
43
|
+
@name = name
|
44
|
+
@description = description
|
45
|
+
@block = block
|
46
|
+
@offline = offline
|
47
|
+
@usage = usage ? usage : ""
|
48
|
+
end
|
49
|
+
|
50
|
+
# Runs the command with the given arguments
|
51
|
+
def run(args)
|
52
|
+
if (@offline)
|
53
|
+
@block.call(args)
|
54
|
+
else
|
55
|
+
previous_token = CLIConfig.instance.config[:current_token]
|
56
|
+
handle = Gliffy::Handle.new(CLIConfig.instance.config[:username],previous_token)
|
57
|
+
@block.call(handle,args)
|
58
|
+
CLIConfig.instance.config[:current_token] = handle.current_token
|
59
|
+
CLIConfig.instance.save
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Executes the command line that was given
|
64
|
+
def self.execute(argv)
|
65
|
+
globals = Hash.new
|
66
|
+
command = argv.shift
|
67
|
+
while !command.nil? && (command =~ /^-/) && !argv.empty?
|
68
|
+
globals[command] = true
|
69
|
+
command = argv.shift
|
70
|
+
end
|
71
|
+
Gliffy::Config.config.log_level = Logger::DEBUG if globals['-v']
|
72
|
+
cmd = Gliffy::Command.commands[command.to_sym]
|
73
|
+
if cmd
|
74
|
+
cmd.run argv
|
75
|
+
else
|
76
|
+
puts "Unknown command #{command}"
|
77
|
+
Gliffy::Command.commands[:help].run []
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Represents the configuration for the command line client.
|
83
|
+
# This is a singleton
|
84
|
+
class CLIConfig
|
85
|
+
|
86
|
+
@@instance = nil
|
87
|
+
|
88
|
+
# Access to the singleton
|
89
|
+
def self.instance
|
90
|
+
@@instance = CLIConfig.new if !@@instance
|
91
|
+
@@instance
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the config hash
|
95
|
+
attr_reader :config
|
96
|
+
|
97
|
+
# Loads the user's rc file if it exists, creating it if not
|
98
|
+
def load
|
99
|
+
if !File.exist?(@config_file_name)
|
100
|
+
puts "#{@config_file_name} not found. Create? [y/n]"
|
101
|
+
answer = STDIN.gets
|
102
|
+
if answer =~ /^[Yy]/
|
103
|
+
save
|
104
|
+
else
|
105
|
+
puts "Aborting..."
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
end
|
109
|
+
yaml_config = File.open(@config_file_name) { |file| YAML::load(file) }
|
110
|
+
if (yaml_config)
|
111
|
+
@config = yaml_config
|
112
|
+
end
|
113
|
+
@config[:open_url] = default_open_url if !@config[:open_url]
|
114
|
+
@config[:open_image] = default_open_image if !@config[:open_image]
|
115
|
+
read_config_from_user('API Key',:api_key)
|
116
|
+
read_config_from_user('Secret Key',:secret_key)
|
117
|
+
read_config_from_user('Account Name',:account_name)
|
118
|
+
read_config_from_user('Username',:username)
|
119
|
+
save
|
120
|
+
config = Gliffy::Config.config
|
121
|
+
@config.each() do |key,value|
|
122
|
+
method = key.to_s + "="
|
123
|
+
if config.respond_to? method.to_sym
|
124
|
+
if (method == 'log_device=')
|
125
|
+
if (value == 'STDERR')
|
126
|
+
config.log_device = STDERR
|
127
|
+
elsif (value == 'STDOUT')
|
128
|
+
config.log_device = STDOUT
|
129
|
+
else
|
130
|
+
config.log_device = value
|
131
|
+
end
|
132
|
+
else
|
133
|
+
config.send(method.to_sym,value)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Saves the configration to the user's config file name as YAML
|
140
|
+
def save
|
141
|
+
fp = File.open(@config_file_name,'w') { |out| YAML::dump(@config,out) }
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
def read_config_from_user(name,symbol)
|
146
|
+
if (!@config[symbol])
|
147
|
+
puts "No #{name} configured. Enter #{name}"
|
148
|
+
@config[symbol] = STDIN.gets.chomp!
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def initialize
|
153
|
+
@config_file_name = File.expand_path("~/.gliffyrc")
|
154
|
+
@config = {
|
155
|
+
:log_level => Gliffy::Config.config.log_level,
|
156
|
+
:log_device => Gliffy::Config.config.log_device.to_s,
|
157
|
+
:gliffy_app_root => Gliffy::Config.config.gliffy_app_root,
|
158
|
+
:gliffy_rest_context => Gliffy::Config.config.gliffy_rest_context,
|
159
|
+
:protocol => Gliffy::Config.config.protocol,
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
def default_open_url
|
164
|
+
# Cheesy defaults
|
165
|
+
if RUBY_PLATFORM =~ /win32/
|
166
|
+
# not sure, acutally
|
167
|
+
nil
|
168
|
+
elsif RUBY_PLATFORM =~ /linux/
|
169
|
+
'firefox "%s"'
|
170
|
+
elsif RUBY_PLATFORM =~ /darwin/
|
171
|
+
'open "%s"'
|
172
|
+
else
|
173
|
+
nil
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def default_open_image
|
178
|
+
# Cheesy defaults
|
179
|
+
if RUBY_PLATFORM =~ /win32/
|
180
|
+
# not sure, acutally
|
181
|
+
nil
|
182
|
+
elsif RUBY_PLATFORM =~ /linux/
|
183
|
+
# not sure
|
184
|
+
nil
|
185
|
+
elsif RUBY_PLATFORM =~ /darwin/
|
186
|
+
'open "%s"'
|
187
|
+
else
|
188
|
+
nil
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
# For defining commands, this specifies the description of the next command defined
|
196
|
+
def desc(description)
|
197
|
+
@next_desc = description
|
198
|
+
end
|
199
|
+
|
200
|
+
# For defining commands, this specifies if the next defined command is "offline"
|
201
|
+
def offline(bool)
|
202
|
+
@next_offline = bool
|
203
|
+
end
|
204
|
+
|
205
|
+
# For defining commands, specifies the usage statement of the next command
|
206
|
+
def usage(usage='')
|
207
|
+
@next_usage = usage
|
208
|
+
end
|
209
|
+
|
210
|
+
# defines a command. The only options supported is ":aliases" which is an array of aliases
|
211
|
+
# for the command
|
212
|
+
def command(name,options={},&block)
|
213
|
+
Command.commands[name] = Command.new(name,@next_desc,@next_offline,@next_usage,block)
|
214
|
+
if options[:aliases]
|
215
|
+
options[:aliases].each() do |a|
|
216
|
+
Command.commands[a] = Command.commands[name]
|
217
|
+
Command.aliases[a] = true
|
218
|
+
end
|
219
|
+
end
|
220
|
+
@next_usage = nil
|
221
|
+
@next_offline = false
|
222
|
+
@next_desc = nil
|
223
|
+
end
|
224
|
+
|
225
|
+
def parse_options(args)
|
226
|
+
options = Hash.new
|
227
|
+
return options if !args
|
228
|
+
i = 0
|
229
|
+
while i < args.length
|
230
|
+
inc = 2
|
231
|
+
if args[i] =~ /^-/
|
232
|
+
arg = args[i].gsub(/^-/,'')
|
233
|
+
if !args[i+1] || (args[i+1] =~ /^-/)
|
234
|
+
inc = 1
|
235
|
+
options[arg] = true
|
236
|
+
else
|
237
|
+
options[arg] = args[i+1]
|
238
|
+
end
|
239
|
+
else
|
240
|
+
break
|
241
|
+
end
|
242
|
+
i += inc
|
243
|
+
end
|
244
|
+
i.times { args.shift }
|
245
|
+
options
|
246
|
+
end
|
247
|
+
|
248
|
+
def format_date(date)
|
249
|
+
date.strftime('%m/%d/%y %H:%M')
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
include Gliffy
|
255
|
+
require 'gliffy/commands/commands'
|
256
|
+
if !CLIConfig.instance.load
|
257
|
+
exit -1
|
258
|
+
end
|
259
|
+
|
260
|
+
|
@@ -0,0 +1,149 @@
|
|
1
|
+
desc 'List all diagrams in the account'
|
2
|
+
usage <<eos
|
3
|
+
[-l]
|
4
|
+
|
5
|
+
-l - show all information
|
6
|
+
eos
|
7
|
+
command :list, :aliases => [:ls] do |gliffy,args|
|
8
|
+
diagrams = gliffy.get_diagrams
|
9
|
+
options = parse_options(args)
|
10
|
+
if options['l']
|
11
|
+
max = diagrams.inject(0) { |max,diagram| diagram.name.length > max ? diagram.name.length : max }
|
12
|
+
diagrams.sort.each do |diagram|
|
13
|
+
printf "%8d %s %-#{max}s %-3d %s %s %s\n",
|
14
|
+
diagram.id,
|
15
|
+
diagram.is_public? ? "P" : "-",
|
16
|
+
diagram.name,
|
17
|
+
diagram.num_versions,
|
18
|
+
format_date(diagram.create_date),
|
19
|
+
format_date(diagram.mod_date),
|
20
|
+
diagram.owner_username
|
21
|
+
end
|
22
|
+
else
|
23
|
+
printf_string = "%d %s\n"
|
24
|
+
diagrams.sort.each { |diagram| printf printf_string,diagram.id,diagram.name }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'Delete a diagram'
|
29
|
+
usage 'diagram_id'
|
30
|
+
command :delete, :aliases => [:del,:rm] do |gliffy,args|
|
31
|
+
gliffy.delete_diagram(args[0])
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'Get the URL for an image'
|
35
|
+
usage <<eos
|
36
|
+
[-o] diagram_id
|
37
|
+
|
38
|
+
-o - open diagram's URL with configured :open_url command
|
39
|
+
eos
|
40
|
+
command :url do |gliffy,args|
|
41
|
+
open = args[0] == '-o'
|
42
|
+
args.shift if open
|
43
|
+
url = gliffy.get_diagram_as_url(args[0])
|
44
|
+
if open
|
45
|
+
if CLIConfig.instance.config[:open_image]
|
46
|
+
system(sprintf(CLIConfig.instance.config[:open_image],url))
|
47
|
+
else
|
48
|
+
puts "Nothing configured for #{:open_image.to_s} to open the image"
|
49
|
+
puts url
|
50
|
+
end
|
51
|
+
else
|
52
|
+
puts url
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
desc 'Download a diagram as an image to a file'
|
57
|
+
usage <<eos
|
58
|
+
[-v version_num] [-f filename] [-t image_type] diagram_id
|
59
|
+
|
60
|
+
image_type can be :jpeg, :png, :svg, or :xml
|
61
|
+
eos
|
62
|
+
command :get do |gliffy,args|
|
63
|
+
|
64
|
+
options = parse_options(args)
|
65
|
+
diagram_id = args.shift
|
66
|
+
|
67
|
+
version_number = options['v'].to_i if options['v']
|
68
|
+
filename = options['f'] if options['f']
|
69
|
+
type = options['t'].to_sym if options['t']
|
70
|
+
type = :jpeg if !type
|
71
|
+
if !filename
|
72
|
+
if version_number
|
73
|
+
filename = "#{diagram_id}_v#{version_number}.#{type.to_s}"
|
74
|
+
else
|
75
|
+
filename = "#{diagram_id}.#{type.to_s}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
get_options = { :mime_type => type, :file => filename }
|
80
|
+
get_options[:version] = version_number if version_number
|
81
|
+
gliffy.get_diagram_as_image(diagram_id,get_options)
|
82
|
+
puts filename
|
83
|
+
end
|
84
|
+
|
85
|
+
desc 'Edit a diagram'
|
86
|
+
usage 'diagram_id'
|
87
|
+
command :edit do |gliffy,args|
|
88
|
+
return_link = gliffy.get_diagram_as_url(args[0])
|
89
|
+
link = gliffy.get_edit_diagram_link(args[0],return_link,"Done")
|
90
|
+
if CLIConfig.instance.config[:open_url]
|
91
|
+
system(sprintf(CLIConfig.instance.config[:open_url],link.full_url))
|
92
|
+
else
|
93
|
+
puts "Nothing configured for #{:open_url.to_s} to open the url"
|
94
|
+
puts link.full_url
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
desc 'Create a new diagram'
|
99
|
+
usage <<eos
|
100
|
+
[-e] diagram_name
|
101
|
+
|
102
|
+
-e edit the diagram after creating it
|
103
|
+
eos
|
104
|
+
command :new do |gliffy,args|
|
105
|
+
edit = args[0] == '-e'
|
106
|
+
args.shift if edit
|
107
|
+
diagram = gliffy.create_diagram(args[0])
|
108
|
+
if edit
|
109
|
+
Gliffy::Command.commands[:edit].run [diagram.id]
|
110
|
+
else
|
111
|
+
puts "#{diagram.name} created with id #{diagram.id}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
desc 'Show commands'
|
116
|
+
offline true
|
117
|
+
usage 'command'
|
118
|
+
command :help do |args|
|
119
|
+
if args[0]
|
120
|
+
command = Command.commands[args[0].to_sym]
|
121
|
+
if command
|
122
|
+
printf "%s - %s\n",args[0],command.description
|
123
|
+
printf "usage: %s %s\n",args[0],command.usage
|
124
|
+
else
|
125
|
+
puts "No such command #{args[0]}"
|
126
|
+
end
|
127
|
+
else
|
128
|
+
command_string = " %-6s %s %s\n"
|
129
|
+
puts 'usage: gliffy [global_options] command [command_options]'
|
130
|
+
puts 'global_options:'
|
131
|
+
Command::GLOBAL_FLAGS.keys.sort.each do |flag|
|
132
|
+
printf command_string,flag,Command::GLOBAL_FLAGS[flag],''
|
133
|
+
end
|
134
|
+
puts
|
135
|
+
puts 'command:'
|
136
|
+
command_names = Command.commands.keys.sort { |a,b| a.to_s <=> b.to_s }
|
137
|
+
command_names.each() do |name|
|
138
|
+
command = Command.commands[name]
|
139
|
+
if !Command.aliases[name]
|
140
|
+
aliases = Array.new
|
141
|
+
Command.aliases.keys.each() do |a|
|
142
|
+
aliases << a if Command.commands[a] == command
|
143
|
+
end
|
144
|
+
alias_string = '(also: ' + aliases.join(',') + ')' if aliases.length > 0
|
145
|
+
printf command_string,name.to_s, command.description,alias_string
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|