departr 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/Gemfile ADDED
@@ -0,0 +1 @@
1
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ departr (0.1)
5
+ haml (~> 3.1)
6
+ json (~> 1.6)
7
+ sass (~> 3.1)
8
+ sinatra (~> 1.3)
9
+ tzinfo (~> 0.3)
10
+
11
+ GEM
12
+ specs:
13
+ haml (3.1.4)
14
+ json (1.6.5)
15
+ rack (1.4.0)
16
+ rack-protection (1.2.0)
17
+ rack
18
+ sass (3.1.12)
19
+ sinatra (1.3.2)
20
+ rack (~> 1.3, >= 1.3.6)
21
+ rack-protection (~> 1.2)
22
+ tilt (~> 1.3, >= 1.3.3)
23
+ tilt (1.3.3)
24
+ tzinfo (0.3.31)
25
+
26
+ PLATFORMS
27
+ ruby
28
+
29
+ DEPENDENCIES
30
+ departr!
data/config.ru.example ADDED
@@ -0,0 +1,16 @@
1
+ require "rubygems"
2
+ require File.join(File.dirname(__FILE__), "lib", "departr")
3
+
4
+ app = Departr::Server.new do
5
+ set :environment, :production
6
+ set :data_path, File.join(File.dirname(__FILE__), "data")
7
+
8
+ set :rpx, {
9
+ :api_key => '........................................',
10
+ :base_url => 'https://rpxnow.com',
11
+ :realm => '................'
12
+ }
13
+ end
14
+
15
+ run app
16
+
data/departr.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "departr"
5
+ s.version = 0.2
6
+ s.date = '2012-01-24'
7
+ s.platform = Gem::Platform::RUBY
8
+ s.author = "Florent Solt"
9
+ s.email = "florent@solt.biz"
10
+ s.homepage = "https://github.com/florentsolt/departr"
11
+ s.summary = "Departr is a smart and fast startpage to help you reach other web sites."
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+
18
+ s.add_runtime_dependency("json", ["~> 1.6"])
19
+ s.add_runtime_dependency("tzinfo", ["~> 0.3"])
20
+ s.add_runtime_dependency("haml", ["~> 3.1"])
21
+ s.add_runtime_dependency("sass", ["~> 3.1"])
22
+ s.add_runtime_dependency("sinatra", ["~> 1.3"])
23
+ end
@@ -0,0 +1,56 @@
1
+ module Departr
2
+ module Command
3
+ extend self
4
+
5
+ def filename(provider, user)
6
+ File.join(Config.data_path, provider, user, 'commands')
7
+ end
8
+
9
+ def get(provider, user)
10
+ JSON.parse(File.read(filename(provider, user))) rescue sort(Config.commands)
11
+ end
12
+
13
+ def time(provider, user)
14
+ if File.exists? filename(provider, user)
15
+ File.mtime(filename(provider, user))
16
+ else
17
+ nil
18
+ end
19
+ end
20
+
21
+ def etag(provider, user)
22
+ begin
23
+ Digest::MD5.hexdigest(time(provider, user).to_s)
24
+ rescue
25
+ '-no-command-file-'
26
+ end
27
+ end
28
+
29
+ def default
30
+ Config.commands
31
+ end
32
+
33
+ def add(provider, user, command)
34
+ save(provider, user, get(provider, user) + [command])
35
+ command
36
+ end
37
+
38
+ def save(provider, user, commands)
39
+ File.open(filename(provider, user), 'w') do |fd|
40
+ fd.write sort(commands).to_json
41
+ end
42
+ end
43
+
44
+ def revert(provider, user)
45
+ File.unlink(filename(provider, user))
46
+ end
47
+
48
+ def sort(commands)
49
+ commands.sort do |a,b|
50
+ a = a['name'].gsub(/\{\w+\}/, '')
51
+ b = b['name'].gsub(/\{\w+\}/, '')
52
+ a <=> b
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,65 @@
1
+ module Departr
2
+ module Config
3
+ extend self
4
+
5
+ @data = {
6
+ :data_path => "/tmp",
7
+
8
+ # Default settings
9
+
10
+ :open_in_new_page => false,
11
+ :default_search => "http://www.google.com/search?ie=UTF-8&oe=UTF-8&q=",
12
+
13
+ :github => true,
14
+ :calendar => true,
15
+ :clock1 => "Europe/Paris",
16
+ :clock2 => "America/New_York",
17
+ :clock3 => "America/Los_Angeles",
18
+
19
+ # Default commands
20
+
21
+ :commands => [
22
+ {'name' => "google search for {words}", 'url' => "http://www.google.com/search?rls=en&q={words}&ie=UTF-8&oe=UTF-8"},
23
+ {'name' => "google search for {words} in {lang}", 'url' => "http://www.google.com/search?rls={lang}&q={words}&ie=UTF-8&oe=UTF-8"},
24
+ {'name' => "wikipedia search for {words}", 'url' => "http://en.wikipedia.org/wiki/{words}"},
25
+ {'name' => "twitter search for {words}", 'url' => "http://search.twitter.com/search?q={words}"},
26
+ {'name' => "youtube search for {words}", 'url' => "http://www.youtube.com/results?search_query={words}"},
27
+ {'name' => "dailymotion search for {words}", 'url' => "http://www.dailymotion.com/relevance/search/{words}"},
28
+ {'name' => "facebook search for {words}", 'url' => "http://www.facebook.com/search/?q={words}"},
29
+ {'name' => "news search for {words}", 'url' => "http://news.google.com/news/search?q={words}"},
30
+ {'name' => "images search for {words}", 'url' => "http://images.google.com/images?q={words}"},
31
+ {'name' => "maps to {where}", 'url' => "http://maps.google.com?q={where}"},
32
+ {'name' => "translate {words} in french", 'url' => "http://www.google.com/translate_t?langpair=en|fr&q={words}"},
33
+ {'name' => "translate {words} in english", 'url' => "http://www.google.com/translate_t?langpair=fr|en&q={words}"},
34
+ {'name' => "php search for {word}", 'url' => "http://www.php.net/{word}"},
35
+ {'name' => "delicious search for {tag}", 'url' => "http://delicious.com/tag/{tag}"},
36
+ {'name' => "flickr search for {tag}", 'url' => "http://www.flickr.com/photos/tags/{tag}"},
37
+ {'name' => "lifehacker search for {words}", 'url' => "http://lifehacker.com/search/{words}/"},
38
+ {'name' => "techcrunch search for {words}", 'url' => "http://search.techcrunch.com/query.php?s={words}"},
39
+ {'name' => "down for everyone or just me {domain}", 'url' => "http://downforeveryoneorjustme.com/{domain}"},
40
+ {'name' => "netvibes", 'url' => "http://www.netvibes.com/"},
41
+ {'name' => "flickr", 'url' => "http://www.flickr.com/"},
42
+ {'name' => "facebook", 'url' => "http://www.facebook.com"},
43
+ {'name' => "twitter", 'url' => "http://www.twitter.com"},
44
+ {'name' => "gist", 'url' => "https://gist.github.com"}
45
+ ]
46
+ }
47
+
48
+ def set(key, val = nil, &blk)
49
+ if val.is_a? Hash and @data.key? key
50
+ @data[key].update val
51
+ else
52
+ @data[key] = block_given? ? blk : val
53
+ end
54
+ end
55
+
56
+ def get(key)
57
+ @data[key]
58
+ end
59
+
60
+ def method_missing(name, *args, &blk)
61
+ @data[name]
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,199 @@
1
+ # encoding: utf-8
2
+
3
+ module Departr
4
+ class Server < Sinatra::Base
5
+
6
+ #-----------------------------------------------------------------------------
7
+ # Constructor
8
+ #-----------------------------------------------------------------------------
9
+
10
+ def initialize(*args, &blk)
11
+ super
12
+ Departr::Config.instance_eval(&blk) if block_given?
13
+ @config = Config
14
+
15
+ # when called in the constructor, it remains persistant
16
+ # if not, it's rebuild every time
17
+ javascripts! if production?
18
+ end
19
+
20
+ def javascripts!
21
+ return @javascripts if not @javascripts.nil?
22
+ @javascripts = ''
23
+ Dir[File.join(settings.root, 'public', 'javascripts', '*.js')].sort.delete_if do |file|
24
+ not File.basename(file).match(/^\d+_/)
25
+ end.each do |file|
26
+ @javascripts << "\n/* #{File.basename(file)} */\n"
27
+ @javascripts << File.read(file)
28
+ end
29
+ @javascripts_checksum = Digest::SHA1.hexdigest(@javascripts)
30
+ @javascripts = JSMin.minify(@javascripts) if production?
31
+ end
32
+
33
+ #-----------------------------------------------------------------------------
34
+ # Sinatra settings
35
+ #-----------------------------------------------------------------------------
36
+
37
+ set :root, File.join(File.dirname(__FILE__), '..', '..')
38
+
39
+ set :sass, {
40
+ :cache_store => Sass::CacheStores::Memory.new,
41
+ :style => production? ? :compressed : :expanded
42
+ }
43
+
44
+ set :haml, {
45
+ :format => :xhtml,
46
+ :ugly => production?
47
+ }
48
+
49
+ set :reload_templates, true if development?
50
+
51
+ #-----------------------------------------------------------------------------
52
+ # Helpers
53
+ #-----------------------------------------------------------------------------
54
+
55
+ helpers do
56
+ include Rack::Utils
57
+ alias_method :h, :escape_html
58
+
59
+ def host
60
+ "http://#{request.host}#{request.port == 80 ? '' : ':' + request.port.to_s}"
61
+ end
62
+
63
+ def auth!
64
+ throw(:halt, [401, "Not authorized\n"]) if not auth?
65
+ end
66
+
67
+ def auth?
68
+ @provider = request.cookies["provider"]
69
+ @user = request.cookies["user"]
70
+ @session = request.cookies["session"]
71
+ check = Session.valid?(@provider, @user, @session)
72
+ if !check
73
+ puts "Invalid session for #{@provider.inspect}, #{@user.inspect}, #{@session.inspect}"
74
+ end
75
+ check
76
+ end
77
+
78
+ def clock_label(tz)
79
+ return ''.to_json if not tz
80
+ tz.split('/').last.gsub('_', ' ').to_json
81
+ end
82
+
83
+ def clock_offset(tz)
84
+ return 0 if not tz
85
+ TZInfo::Timezone.get(tz).current_period.utc_total_offset / 3600
86
+ end
87
+ end
88
+
89
+ #-----------------------------------------------------------------------------
90
+ # Javascripts and stylesheets
91
+ #-----------------------------------------------------------------------------
92
+
93
+ get '/javascripts/all.js' do
94
+ content_type :js
95
+ javascripts!
96
+ etag "js-#{@javascripts_checksum}" if production?
97
+ @javascripts
98
+ end
99
+
100
+ get '/stylesheets/all.css' do
101
+ content_type :css
102
+ if production?
103
+ time = File.mtime(File.join(settings.root, 'views', 'style.sass'))
104
+ etag "css-#{time.to_i}"
105
+ end
106
+ sass :style
107
+ end
108
+
109
+ #-----------------------------------------------------------------------------
110
+ # Session
111
+ #-----------------------------------------------------------------------------
112
+
113
+ post '/signin' do
114
+ if @config.get(:rpx)
115
+ token = params[:token]
116
+ rpx = Rpx::RpxHelper.new(@config.get(:rpx)[:api_key], @config.get(:rpx)[:base_url], @config.get(:rpx)[:realm])
117
+ profile = rpx.auth_info(token, request.url)
118
+ user, session = Session.signin(profile)
119
+ response.set_cookie("session", :value => session, :path => '/', :expires => Time.now + 60*60*24*365)
120
+ response.set_cookie("user", :value => user, :path => '/', :expires => Time.now + 60*60*24*365)
121
+ response.set_cookie("name", :value => profile['preferredUsername'], :path => '/', :expires => Time.now + 60*60*24*365)
122
+ response.set_cookie("provider", :value => profile['providerName'].downcase, :path => '/', :expires => Time.now + 60*60*24*365)
123
+ end
124
+ redirect '/'
125
+ end
126
+
127
+ get '/profile' do
128
+ auth!
129
+ Session.profile(@provider, @user, false).to_json
130
+ end
131
+
132
+ #-----------------------------------------------------------------------------
133
+ # Settings
134
+ #-----------------------------------------------------------------------------
135
+
136
+ get '/settings' do
137
+ auth!
138
+ haml :settings, :layout => false
139
+ end
140
+
141
+ post '/settings' do
142
+ auth!
143
+ Settings.save(@provider, @user, JSON.parse(request.body.read))
144
+ status 200
145
+ end
146
+
147
+ #-----------------------------------------------------------------------------
148
+ # Commands
149
+ #-----------------------------------------------------------------------------
150
+
151
+ get '/command/revert' do
152
+ auth!
153
+ @commands = Command.revert(@provider, @user)
154
+ redirect '/'
155
+ end
156
+
157
+ post '/command/save' do
158
+ auth!
159
+ @commands = Command.save(@provider, @user, JSON.parse(request.body.read))
160
+ status 200
161
+ end
162
+
163
+ post '/command/add' do
164
+ auth!
165
+ @commands = Command.add(@provider, @user, JSON.parse(request.body.read))
166
+ status 200
167
+ end
168
+
169
+ get '/command/all' do
170
+ auth!
171
+ content_type :json
172
+ etag "command-" + Command.etag(@provider, @user) if production?
173
+ Command.get(@provider, @user).to_json
174
+ end
175
+
176
+ #-----------------------------------------------------------------------------
177
+ # Index
178
+ #-----------------------------------------------------------------------------
179
+
180
+ get '/help' do
181
+ haml :help
182
+ end
183
+
184
+ get '/' do
185
+ if auth?
186
+ etag "index-#{Command.etag(@provider, @user)}-#{Settings.etag(@provider, @user)}" if production?
187
+ @commands = Command.get(@provider, @user)
188
+ @settings = Settings.get(@provider, @user)
189
+ else
190
+ etag "default-#{Digest::SHA1.hexdigest(Command.default(false))}" if production?
191
+ response.delete_cookie("user")
192
+ response.delete_cookie("session")
193
+ @commands = Command.default
194
+ @settings = Settings.default
195
+ end
196
+ haml :index
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,40 @@
1
+ module Departr
2
+ module Session
3
+ extend self
4
+
5
+ def filename(provider, user, session_or_profile)
6
+ if session_or_profile == :profile
7
+ file = File.join(Config.data_path, provider, user, 'profile')
8
+ else
9
+ file = File.join(Config.data_path, provider, user, 'session', session_or_profile)
10
+ end
11
+ FileUtils.mkdir_p File.dirname(file) if not File.directory? File.dirname(file)
12
+ file
13
+ end
14
+
15
+ def valid?(provider, user, session)
16
+ begin
17
+ File.exists? filename(provider, user, session)
18
+ rescue
19
+ false
20
+ end
21
+ end
22
+
23
+ def signin(profile)
24
+ user = Digest::SHA1.hexdigest(profile['identifier'])
25
+ provider = profile['providerName'].downcase
26
+ File.open(filename(provider, user, :profile), 'w') do |fd|
27
+ fd.write profile.to_json
28
+ end
29
+ session = Digest::SHA1.hexdigest("#{profile['identifier']}#{Time.now}#{rand}")
30
+ File.open(filename(provider, user, session), 'w') do |fd|
31
+ fd.write Time.now.to_s
32
+ end
33
+ [user, session]
34
+ end
35
+
36
+ def profile(provider, user)
37
+ JSON.parse(File.read(filename(provider, user, :profile)))
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,37 @@
1
+ module Departr
2
+ module Settings
3
+ extend self
4
+
5
+ KEYS = [
6
+ :default_search, :open_in_new_page,
7
+ :github, :calendar,
8
+ :clock1, :clock2, :clock3
9
+ ]
10
+
11
+ def filename(provider, user)
12
+ File.join(Config.data_path, provider, user, 'settings')
13
+ end
14
+
15
+ def etag(provider, user)
16
+ Digest::MD5.hexdigest(get(provider, user).to_s)
17
+ end
18
+
19
+ def get(provider, user)
20
+ default.merge(JSON.parse(File.read(filename(provider, user)))) rescue default
21
+ end
22
+
23
+ def save(provider, user, data)
24
+ File.open(filename(provider, user), 'w') do |fd|
25
+ fd.write data.to_json
26
+ end
27
+ end
28
+
29
+ def default
30
+ hash = {}
31
+ # Do not keep symbol because it's save in JSON
32
+ KEYS.each { |key| hash[key.to_s] = Config.get(key) }
33
+ hash
34
+ end
35
+
36
+ end
37
+ end
data/lib/departr.rb ADDED
@@ -0,0 +1,24 @@
1
+ if defined? Encoding
2
+ Encoding.default_external = Encoding::UTF_8
3
+ Encoding.default_internal = Encoding::UTF_8
4
+ end
5
+
6
+ $: << File.dirname(__FILE__) unless $:.include? File.dirname(__FILE__)
7
+ require "digest/sha1"
8
+ require "json"
9
+ require "tzinfo"
10
+ require "haml"
11
+ require "sass"
12
+ require "fileutils"
13
+ require "net/http"
14
+ require "sinatra"
15
+
16
+ # TODO: use gems
17
+ require "rpx"
18
+ require "jsmin"
19
+
20
+ require "departr/server"
21
+ require "departr/config"
22
+ require "departr/settings"
23
+ require "departr/session"
24
+ require "departr/commands"