yetty 0.0.1 → 0.1.0

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.
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