yetty 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -0
  3. data/LICENSE +0 -0
  4. data/README.md +66 -0
  5. data/bin/yetty +9 -4
  6. data/lib/yetty.rb +15 -0
  7. data/lib/yetty/command.rb +136 -0
  8. data/lib/yetty/play.rb +23 -0
  9. data/lib/yetty/push.rb +44 -0
  10. data/lib/yetty/record.rb +73 -0
  11. data/lib/yetty/site.rb +23 -0
  12. data/lib/yetty/site/app.rb +91 -0
  13. data/lib/yetty/site/static/assets/term/bg-gradient.png +0 -0
  14. data/lib/yetty/site/static/config.rb +26 -0
  15. data/lib/yetty/site/static/css/fd-slider.css +1004 -0
  16. data/lib/yetty/site/static/css/fonts/bootstrap/glyphicons-halflings-regular.eot +0 -0
  17. data/lib/yetty/site/static/css/fonts/bootstrap/glyphicons-halflings-regular.svg +229 -0
  18. data/lib/yetty/site/static/css/fonts/bootstrap/glyphicons-halflings-regular.ttf +0 -0
  19. data/lib/yetty/site/static/css/fonts/bootstrap/glyphicons-halflings-regular.woff +0 -0
  20. data/lib/yetty/site/static/css/player.css +165 -0
  21. data/lib/yetty/site/static/css/styles.css +8035 -0
  22. data/lib/yetty/site/static/js/bootstrap.js +2323 -0
  23. data/lib/yetty/site/static/js/bootstrap.min.js +12 -0
  24. data/lib/yetty/site/static/js/config.json +429 -0
  25. data/lib/yetty/site/static/js/jquery-2.1.1.min.js +4 -0
  26. data/lib/yetty/site/static/js/nyan/runner.js +92 -0
  27. data/lib/yetty/site/static/js/nyan/three.min.js +724 -0
  28. data/lib/yetty/site/static/js/nyan/threex.nyancat.js +193 -0
  29. data/lib/yetty/site/static/js/nyan/threex.nyancatrainbow.js +89 -0
  30. data/lib/yetty/site/static/js/nyan/threex.nyancatsound.js +33 -0
  31. data/lib/yetty/site/static/js/nyan/threex.nyancatstars.js +116 -0
  32. data/lib/yetty/site/static/js/player/fd-slider.min.js +2 -0
  33. data/lib/yetty/site/static/js/player/player.js +371 -0
  34. data/lib/yetty/site/static/js/player/term.js +4177 -0
  35. data/lib/yetty/site/static/sass/_bootstrap-variables.scss +818 -0
  36. data/lib/yetty/site/static/sass/player.scss +139 -0
  37. data/lib/yetty/site/static/sass/styles.scss +3 -0
  38. data/lib/yetty/site/static/sounds/nyanlooped.mp3 +0 -0
  39. data/lib/yetty/site/static/sounds/nyanslow.mp3 +0 -0
  40. data/lib/yetty/site/views/about.haml +8 -0
  41. data/lib/yetty/site/views/index.haml +13 -0
  42. data/lib/yetty/site/views/layout.haml +33 -0
  43. data/lib/yetty/site/views/recording.haml +34 -0
  44. data/lib/yetty/site/views/recordings.haml +7 -0
  45. data/lib/yetty/site/views/user.haml +28 -0
  46. data/lib/yetty/site/views/users.haml +16 -0
  47. data/lib/yetty/ui.rb +103 -0
  48. data/lib/yetty/user.rb +42 -0
  49. data/lib/yetty/version.rb +4 -0
  50. data/yetty.gemspec +21 -0
  51. metadata +49 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d62402f9a45233864b3273f6cbcef84e06426857
4
- data.tar.gz: 4efde678a3eec7a504d3ffbbce4f993c0f589d84
3
+ metadata.gz: 7f45bd672b89d1030a4d3dc6c661c9509feb07d0
4
+ data.tar.gz: 8e357c6886ad138f1c56cd727ac340d62fcad281
5
5
  SHA512:
6
- metadata.gz: 8ce82e5ceb45e840bb67573a7491feb292318651a2072a16bc29fd85359d4fb1a0b17ba2bf4dde0efb968406da396ee01d68c52b0dc3f30d37999e6172d82806
7
- data.tar.gz: 24c52b6820665db19107c9f1767c76be40280fea1d82a17780c611c90d561159c65c27cb4326826209e1b94ac6fbc423d2aedf99cd7f2f2b4a043e7bdd0826a7
6
+ metadata.gz: a98db022addf0ad2ff0ad3fc2864454e9ad365b666620331cd73b665b23358a8aad67855a907fe2c132434392f1992540d0a1a1511fee92158cd3a1ab9cc97b6
7
+ data.tar.gz: 21bad94340f8d3769204c587cdc4f0678aac9c87160f1b6aed8af17d7cbc24784fcd9f755dc8d6be4b2c9bea6caba2f5605d1ac0d889171112f86266c829bae8
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## v0.1.0
2
+ * Initial release
data/LICENSE ADDED
File without changes
data/README.md CHANGED
@@ -0,0 +1,66 @@
1
+ ## Yetty
2
+
3
+ With shelr.tv[1] now defunct, I wanted an easy way to spin out
4
+ my own to easily share terminal recordings. Yetty is a wrapper
5
+ around shelr[2] that removes some of the extra features it
6
+ provids. It also has a very simple web UI builtin that hooks into
7
+ an object store to persist dump files.
8
+
9
+ ### Is it secure?
10
+
11
+ No, not at all. This is configuration based and user uploads are
12
+ simply prefixed within the bucket. But this is just a friendly
13
+ tool, so if bad things happen, maybe your friends aren't so friendly.
14
+
15
+ ### Web UI?
16
+
17
+ Yep. It's a very simple sinatra application. It pulls information
18
+ directly from the object store. So not built for speed or scale.
19
+
20
+ ### Private?
21
+
22
+ Kinda. Private pushes are simply not listed publicly. So you'll need
23
+ the direct link to access them (much like private gists).
24
+
25
+ ### Configuration
26
+
27
+ Configuration is a JSON file. Locations checked in order of precedence:
28
+
29
+ * ./yetty
30
+ * ~/.yetty
31
+ * /etc/yetty/config.json
32
+
33
+ File contents:
34
+
35
+ ```json
36
+ {
37
+ "site": {
38
+ "url": "http://localhost:4000"
39
+ },
40
+ "user": {
41
+ "username": "YOUR_USERNAME",
42
+ "storage": {
43
+ "provider": "aws",
44
+ "bucket": "my-yetty-bucket",
45
+ "credentials": {
46
+ "aws_access_key_id": "...",
47
+ "aws_secret_access_key": "...",
48
+ "aws_bucket_region": "..."
49
+ }
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ The `credentials` section are the credentials expected
56
+ by miasma[3] based on the provider in use.
57
+
58
+ ### Thanks!
59
+
60
+ A big thanks to @antono for the shelr[2] library. Also
61
+ to @jeromeetienne for the beautiful nyancat[4]
62
+
63
+ [1]: https://github.com/antono/shelr.tv "record you shell and publish it"
64
+ [2]: https://github.com/antono/shelr "Console screencasting tool"
65
+ [3]: https://github.com/chrisroberts/miasma "Cloud modeling library"
66
+ [4]: https://github.com/jeromeetienne/threex.nyancat "three.js extension to make nyancat"
data/bin/yetty CHANGED
@@ -23,7 +23,6 @@ begin
23
23
  description 'Push JSON recording'
24
24
  on :c, :config=, 'JSON configuration file'
25
25
  on :p, :private, 'Not publicly listed'
26
- on :o, :overwrite, 'Overwrite if already exists'
27
26
  run do |opts, args|
28
27
  Yetty::Push.new({:push => opts.to_hash}, args).execute!
29
28
  end
@@ -37,11 +36,17 @@ begin
37
36
  end
38
37
  end
39
38
 
39
+ command 'play' do
40
+ description 'Play terminal'
41
+ run do |opts, args|
42
+ Yetty::Play.new({:play => opts}, args).execute!
43
+ end
44
+ end
45
+
40
46
  command 'user' do
41
- description 'View/set user information'
42
- on :c, :config=, 'JSON configuration file'
47
+ description 'Set user information'
43
48
  run do |opts, args|
44
- Yetty::UserInfo.new({:user => opts.to_hash}, args).execute!
49
+ Yetty::User.new({:user => opts.to_hash}, args).execute!
45
50
  end
46
51
  end
47
52
 
data/lib/yetty.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'yetty/version'
2
+
3
+ # TTY utility
4
+ module Yetty
5
+ autoload :Command, 'yetty/command'
6
+ autoload :Play, 'yetty/play'
7
+ autoload :Push, 'yetty/push'
8
+ autoload :Record, 'yetty/record'
9
+ autoload :Site, 'yetty/site'
10
+ autoload :Ui, 'yetty/ui'
11
+ autoload :User, 'yetty/user'
12
+ end
13
+
14
+ require 'miasma'
15
+ require 'miasma/utils/smash'
@@ -0,0 +1,136 @@
1
+ require 'yetty'
2
+
3
+ module Yetty
4
+ # Abstract command class
5
+ class Command
6
+
7
+ # @return [Hash] options
8
+ attr_reader :options
9
+ # @return [Array] cli arguments
10
+ attr_reader :arguments
11
+ # @return [Ui]
12
+ attr_reader :ui
13
+
14
+ # Default configuration file locations
15
+ DEFAULT_CONFIGURATION_FILES = [
16
+ './.yetty',
17
+ '~/.yetty',
18
+ '/etc/yetty/config.json'
19
+ ]
20
+
21
+ # Build new command instance
22
+ #
23
+ # @return [self]
24
+ def initialize(opts, args)
25
+ @options = opts.to_smash
26
+ @arguments = args
27
+ @ui = Ui.new
28
+ load_config!
29
+ end
30
+
31
+ # Execute the command
32
+ #
33
+ # @return [TrueClass]
34
+ def execute!
35
+ raise NotImplementedError
36
+ end
37
+
38
+ protected
39
+
40
+ # Command specific options
41
+ #
42
+ # @return [Hash]
43
+ def opts
44
+ options.fetch(self.class.name.split('::').last.downcase, {})
45
+ end
46
+
47
+ # User specific options
48
+ #
49
+ # @return [Smash]
50
+ def user
51
+ result = options[:user]
52
+ unless(result)
53
+ raise 'No user information defined!'
54
+ else
55
+ result = result.to_smash
56
+ unless(result[:username])
57
+ result[:username] = ENV['USER']
58
+ end
59
+ result
60
+ end
61
+ end
62
+
63
+ # @return [Miasma::Models::Storage]
64
+ def storage
65
+ if(user[:storage])
66
+ Miasma.api(
67
+ :type => :storage,
68
+ :provider => user[:storage][:provider],
69
+ :credentials => user[:storage][:credentials]
70
+ )
71
+ else
72
+ raise 'No remote storage configuration found!'
73
+ end
74
+ end
75
+
76
+ # @return [Miasma::Models::Storage::Bucket]
77
+ def bucket
78
+ unless(user.get(:storage, :bucket))
79
+ raise 'No bucket defined within storage configuration!'
80
+ end
81
+ bucket = storage.buckets.get(user[:storage][:bucket])
82
+ unless(bucket)
83
+ bucket = storage.buckets.build
84
+ bucket.name = user[:storage][:bucket]
85
+ bucket.save
86
+ bucket = storage.buckets.reload.get(user[:storage][:bucket])
87
+ end
88
+ bucket
89
+ end
90
+
91
+ # Load configuration file and merge opts
92
+ # on top of file values
93
+ #
94
+ # @return [Hash]
95
+ def load_config!
96
+ if(options[:config])
97
+ content = File.read(options[:config])
98
+ else
99
+ path = DEFAULT_CONFIGURATION_FILES.detect do |check|
100
+ full_check = File.expand_path(check)
101
+ File.exists?(full_check)
102
+ end
103
+ content = File.read(path) if path
104
+ end
105
+ if(content)
106
+ @options = MultiJson.load(content).to_smash.deep_merge(options)
107
+ end
108
+ end
109
+
110
+ # Wrap action within nice text. Output resulting Hash if provided
111
+ #
112
+ # @param msg [String] message of action in progress
113
+ # @yieldblock action to execute
114
+ # @yieldreturn [Hash] result to output
115
+ # @return [TrueClass]
116
+ def run_action(msg)
117
+ ui.info("#{msg}... ", :nonewline)
118
+ begin
119
+ result = yield
120
+ ui.puts ui.color('complete!', :green, :bold)
121
+ if(result.is_a?(Hash))
122
+ ui.puts '---> Results:'
123
+ result.each do |k,v|
124
+ ui.puts " #{ui.color("#{k}:", :bold)} #{v}"
125
+ end
126
+ end
127
+ rescue => e
128
+ ui.puts ui.color('error!', :red, :bold)
129
+ ui.error "Reason - #{e}"
130
+ raise
131
+ end
132
+ true
133
+ end
134
+
135
+ end
136
+ end
data/lib/yetty/play.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'yetty'
2
+ require 'shelr'
3
+
4
+ module Yetty
5
+ class Player < Shelr::Player
6
+ end
7
+ end
8
+
9
+ module Yetty
10
+ # Play command class
11
+ class Play < Command
12
+
13
+ # Invoke shelr record
14
+ def execute!
15
+ if(arguments.first.start_with?('http'))
16
+ Yetty::Player.play_remote(arguments.first)
17
+ else
18
+ Yetty::Player.play_dump(arguments.first)
19
+ end
20
+ end
21
+
22
+ end
23
+ end
data/lib/yetty/push.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'yetty'
2
+
3
+ module Yetty
4
+ # Push command class
5
+ class Push < Command
6
+
7
+ # Push shelr json to storage
8
+ def execute!
9
+ run_action('Updating user list') do
10
+ file = bucket.files.get('userlist.json')
11
+ content = file ? MultiJson.load(file.body.readpartial).to_smash : Smash.new(:users => [])
12
+ unless(content[:users].include?(user[:username]))
13
+ content[:users].push(user[:username]).uniq!
14
+ file = bucket.files.build
15
+ file.name = 'userlist.json'
16
+ file.content_type = 'application/json'
17
+ file.body = MultiJson.dump(content)
18
+ file.save
19
+ end
20
+ end
21
+ filename = arguments.first
22
+ run_action("Pushing file #{filename}") do
23
+ key_name = File.basename(filename).sub(/\.[^\.]+$/, '')
24
+ file_content = File.read(filename)
25
+ data = MultiJson.load(file_content).to_smash
26
+ state = data[:private] || options[:push][:private] ? 'private' : 'public'
27
+ key_name = File.join(user[:username], state, "#{Time.now.to_i}-#{key_name}.json")
28
+ file = bucket.files.build
29
+ file.name = key_name
30
+ file.content_type = 'application/json'
31
+ file.body = file_content
32
+ file.save
33
+ encoded_key = Base64.urlsafe_encode64(file.name)
34
+ Smash.new(
35
+ :bucket => bucket.name,
36
+ :key => file.name,
37
+ :encoded_key => encoded_key,
38
+ :link => options.get(:site, :url) ? File.join(options[:site][:url], 'recording', encoded_key) : 'Not configured!'
39
+ )
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,73 @@
1
+ require 'yetty'
2
+ require 'shelr'
3
+
4
+ module Yetty
5
+
6
+ class Publisher < Shelr::Publisher
7
+
8
+ attr_reader :file_name
9
+
10
+ def initialize(file_name='yetty.json')
11
+ @file_name = file_name
12
+ super()
13
+ end
14
+
15
+ def prepare(id)
16
+ out = {}
17
+ ['meta', 'timing', 'typescript'].each do |file|
18
+ out[file] = File.read(File.join(Shelr.data_dir(id), file))
19
+ end
20
+
21
+ meta = JSON.parse(out.delete('meta'))
22
+ meta.each { |k,v| out[k] = v }
23
+ STDOUT.print 'Description: '
24
+ out['description'] = STDIN.gets.strip
25
+ STDOUT.print 'Tags (ex: howto, linux): '
26
+ out['tags'] = STDIN.gets.strip
27
+ out['private'] = @private
28
+ return out.to_json
29
+ end
30
+
31
+ def dump_filename
32
+ file_name
33
+ end
34
+
35
+ end
36
+
37
+ class Recorder < Shelr::Recorder
38
+
39
+ def record!(options={})
40
+ ensure_terminal_has_good_size
41
+ check_record_dir
42
+ with_lock_file do
43
+ init_terminal
44
+ request_metadata
45
+ Shelr.terminal.puts_line
46
+ STDOUT.puts "=> Your session started"
47
+ STDOUT.puts "=> Please, do not resize your terminal while recording"
48
+ STDOUT.puts "=> Press Ctrl+D or 'exit' to finish recording"
49
+ Shelr.terminal.puts_line
50
+ start_sound_recording if options[:sound]
51
+ system(recorder_cmd)
52
+ stop_sound_recording if options[:sound]
53
+ save_as_typescript if Shelr.backend == 'ttyrec'
54
+ Shelr.terminal.puts_line
55
+ STDOUT.puts "=> Session finished"
56
+ end
57
+ end
58
+
59
+ end
60
+ end
61
+
62
+ module Yetty
63
+ # Record command class
64
+ class Record < Command
65
+
66
+ # Invoke shelr record
67
+ def execute!
68
+ Yetty::Recorder.record!(opts)
69
+ Yetty::Publisher.new(arguments.first || 'yetty.json').dump('last')
70
+ end
71
+
72
+ end
73
+ end
data/lib/yetty/site.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'yetty'
2
+
3
+ module Yetty
4
+ class Site < Yetty::Command
5
+
6
+ autoload :App, 'yetty/site/app'
7
+
8
+ # Start the web UI
9
+ #
10
+ # @return true
11
+ def execute!
12
+ if(opts[:port])
13
+ App.port = opts[:port]
14
+ end
15
+ if(opts[:bind])
16
+ App.bind = opts[:bind]
17
+ end
18
+ App.server = options.fetch(:server, 'webrick')
19
+ App.run!
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,91 @@
1
+ require 'base64'
2
+ require 'sinatra/base'
3
+
4
+ module Yetty
5
+ class Site
6
+ # Web UI
7
+ class App < Sinatra::Base
8
+
9
+ def bucket
10
+ storage = Miasma.api(
11
+ :type => :storage,
12
+ :provider => :aws,
13
+ :credentials => {
14
+ :aws_access_key_id => ENV['MIASMA_AWS_ACCESS_KEY_ID'],
15
+ :aws_secret_access_key => ENV['MIASMA_AWS_SECRET_ACCESS_KEY'],
16
+ :aws_region => ENV['MIASMA_AWS_REGION'],
17
+ :aws_bucket_region => 'us-west-2'
18
+ }
19
+ )
20
+ bucket = storage.buckets.get('cr-test-00')
21
+ end
22
+
23
+ set :public_folder, File.join(File.dirname(__FILE__), 'static')
24
+
25
+ get '/' do
26
+ haml :index
27
+ end
28
+
29
+ get '/recordings' do
30
+ haml :recordings
31
+ end
32
+
33
+ get '/recording/:info' do
34
+ rec_path = Base64.urlsafe_decode64(params[:info])
35
+ user, state, name = rec_path.split('/')
36
+ rec_url = bucket.files.get(rec_path).url
37
+ haml(
38
+ :recording,
39
+ :locals => {
40
+ :user => user,
41
+ :state => state,
42
+ :name => name,
43
+ :url => rec_url
44
+ }
45
+ )
46
+ end
47
+
48
+ get '/users' do
49
+ file = bucket.files.get('userlist.json')
50
+ users = file ? MultiJson.load(file.body.readpartial).to_smash[:users] : []
51
+ haml(
52
+ :users,
53
+ :locals => {
54
+ :users => users
55
+ }
56
+ )
57
+ end
58
+
59
+ get '/users/:username' do
60
+ user_file = bucket.files.get(
61
+ File.join(params[:username], 'userinfo.json')
62
+ )
63
+ if(user_file)
64
+ user_info = MultiJson.load(user_file.read).to_smash
65
+ else
66
+ user_info = Smash.new
67
+ end
68
+ prefix = [params[:username], 'public'].join('/')
69
+ records = bucket.files.filter(
70
+ :prefix => [
71
+ params[:username],
72
+ 'public'
73
+ ].join('/')
74
+ )
75
+ haml(
76
+ :user,
77
+ :locals => {
78
+ :username => params[:username],
79
+ :user_info => user_info,
80
+ :records => records
81
+ }
82
+ )
83
+ end
84
+
85
+ get '/about' do
86
+ haml :about
87
+ end
88
+
89
+ end
90
+ end
91
+ end