version50 0.0.1

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/bin/version50 ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'version50'
5
+
6
+ # display help if no options are given
7
+ if ARGV.length == 0
8
+ Version50.new :action => 'help'
9
+ exit
10
+ end
11
+
12
+ # hide ruby-related ctrl-c messages
13
+ trap('INT') {
14
+ puts ''
15
+ exit
16
+ }
17
+
18
+ # pass action to version50 handler
19
+ action = ARGV[0]
20
+ version50 = Version50.new :action => action
data/lib/version50.rb ADDED
@@ -0,0 +1,237 @@
1
+ require 'rubygems'
2
+ require 'version50/git'
3
+ require 'json'
4
+ require 'optparse'
5
+ require 'yaml'
6
+ require 'net/http'
7
+ require 'net/https'
8
+ require 'highline/import'
9
+ HighLine.track_eof = false
10
+
11
+ class Version50
12
+ def initialize(args)
13
+ # parse configuration
14
+ config = self.parse_config
15
+ @scm = self.scm config
16
+
17
+ # no configuration file, so prompt to create a new project
18
+ if !config
19
+ config = self.create
20
+ @scm = self.scm config
21
+ @scm.init
22
+ end
23
+
24
+ # set user info
25
+ @scm.config config
26
+
27
+ # commit a new version without pushing
28
+ if args[:action] == 'commit'
29
+ @scm.commit
30
+ end
31
+
32
+ # view the commit history
33
+ if args[:action] == 'history' || args[:action] == 'log'
34
+ commits = @scm.log
35
+ self.output_history commits
36
+ end
37
+
38
+ # push the current project
39
+ if args[:action] == 'push'
40
+ @scm.push
41
+ end
42
+
43
+ # save a new version, which means commit and push
44
+ if args[:action] == 'save'
45
+ @scm.save
46
+ puts "\n\033[032mSaved a new version!\033[0m"
47
+ end
48
+
49
+ # get the current status of files
50
+ if args[:action] == 'status'
51
+ files = @scm.status
52
+ self.output_status files
53
+ end
54
+
55
+ # warp to a past version
56
+ if args[:action] == 'warp'
57
+ @scm.warp
58
+ end
59
+ end
60
+
61
+ def create
62
+ # prompt for user info
63
+ puts "\nLooks like you're creating a new project!\n\n"
64
+ name = ask("What's your name? ")
65
+ email = ask("And your email? ")
66
+ puts "If you're hosting your project using a service like GitHub or BitBucket, paste the URL here."
67
+ puts "If not, you can just leave this blank!"
68
+ remote = $stdin.gets.chomp
69
+
70
+ # create configuration hash
71
+ config = {
72
+ 'name' => name.to_s,
73
+ 'email' => email.to_s,
74
+ 'remote' => remote.to_s,
75
+ 'scm' => 'git'
76
+ }
77
+
78
+ # prompt to create ssh key if one doesn't exist
79
+ if !File.exists?(File.expand_path '~/.ssh/id_rsa') && !File.exists?(File.expand_path '~/.ssh/id_dsa')
80
+ puts "It looks like you don't have an SSH key!"
81
+ answer = ask("Would you like to create one now? [y/n] ")
82
+
83
+ # user responded with yes, so create key
84
+ if answer == 'y' || answer == 'yes'
85
+ # prompt for password of at length 5
86
+ path = File.expand_path '~/.ssh/id_rsa'
87
+ password = ''
88
+ while password.length < 5
89
+ password = ask("Type a password for your key (at least 5 characters): ") { |q| q.echo = '*' }
90
+ end
91
+
92
+ # use ssh keygen to create key
93
+ `ssh-keygen -q -C "#{email}" -t rsa -N "#{password}" -f #{path}`
94
+ end
95
+ end
96
+
97
+ # prompt to add key to remote account
98
+ if remote =~ /github/
99
+ puts "Would you like to add your key to your GitHub account?"
100
+ answer = ask("If you've already done this, you won't need to do so again! [y/n] ")
101
+
102
+ # prompt for github info
103
+ if answer == 'y' || answer == 'yes'
104
+ # repeat until authentication is successful
105
+ response = nil
106
+ while !response || response.code != '201'
107
+ username = ask("What's your GitHub username? ")
108
+ password = ask("And your GitHub password? ") { |q| q.echo = '*' }
109
+
110
+ # post key to github
111
+ http = Net::HTTP.new('api.github.com', 443)
112
+ http.use_ssl = true
113
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
114
+ request = Net::HTTP::Post.new('/user/keys')
115
+ request['Content-Type'] = 'application/json'
116
+ request.basic_auth username, password
117
+ request.body = {
118
+ 'title' => 'version50',
119
+ 'key' => File.open(File.expand_path('~/.ssh/id_rsa.pub')).gets
120
+ }.to_json
121
+ response = http.request(request)
122
+ end
123
+ end
124
+ end
125
+
126
+ # save config
127
+ File.open(Dir.pwd + '/.version50', 'w') do |f|
128
+ f.write config.to_yaml
129
+ end
130
+
131
+ puts "\n\033[032mYour project was created successfully, now have fun!"
132
+ puts "<3 version50\033[0m"
133
+
134
+ return config
135
+ end
136
+
137
+ # given a parsed SCM history output, show log
138
+ def output_history commits
139
+ # output each commit
140
+ commits.each_with_index do |commit, i|
141
+ puts "\033[031m#%03d \033[0m#{commit[:message]} \033[34m(#{commit[:timestamp]} by #{commit[:author]})" % (commits.length - i)
142
+ end
143
+
144
+ # ansi reset
145
+ print "\033[0m"
146
+ end
147
+
148
+ # given a parsed SCM status output, show file status
149
+ def output_status files
150
+ # new files (ansi green)
151
+ if files[:added].length > 0
152
+ print "\033[32m"
153
+ puts "\nNew Files"
154
+ puts "=========\n\n"
155
+
156
+ files[:added].each do |file|
157
+ puts "* #{file}"
158
+ end
159
+ puts ""
160
+ end
161
+
162
+ # modified files (ansi yellow)
163
+ if files[:modified].length > 0
164
+ print "\033[33m"
165
+ puts "\nModified Files"
166
+ puts "==============\n\n"
167
+
168
+ files[:modified].each do |file|
169
+ puts "* #{file}"
170
+ end
171
+ end
172
+
173
+ # deleted files (ansi red)
174
+ if files[:deleted].length > 0
175
+ print "\033[31m"
176
+ puts "\nDeleted Files"
177
+ puts "=============\n\n"
178
+
179
+ files[:deleted].each do |file|
180
+ puts "* #{file}"
181
+ end
182
+
183
+ puts ""
184
+ end
185
+
186
+ # nothing changed
187
+ if files[:added].length == 0 && files[:modified].length == 0 && files[:deleted].length == 0
188
+ print "Nothing has changed since your last save!"
189
+ end
190
+
191
+ # ansi reset
192
+ print "\033[0m\n"
193
+ end
194
+
195
+ # parse the version50 configuration file
196
+ def parse_config
197
+ # search upward to find project root
198
+ path = self.root
199
+ if path
200
+ return YAML.load_file(path + '/.version50')
201
+ end
202
+
203
+ # project root not found
204
+ return false
205
+ end
206
+
207
+ # get the path of the project root, as determined by the location of the .version50 file
208
+ def root
209
+ # search upward for a file called ".version50"
210
+ path = Pathname.new(Dir.pwd)
211
+ while path.to_s != '/'
212
+ # check if file exists in this directory
213
+ if path.children(false).select { |e| e.to_s == '.version50' }.length > 0
214
+ return path.to_s
215
+ end
216
+
217
+ # continue to traverse upwards
218
+ path = path.parent
219
+ end
220
+
221
+ # .version50 file not found
222
+ return false
223
+ end
224
+
225
+ # determine the scm engine based on the config file
226
+ def scm config
227
+ # no engine specified
228
+ if !config
229
+ return nil
230
+ end
231
+
232
+ # git backend
233
+ if config['scm'] == 'git'
234
+ return Git.new(self)
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,144 @@
1
+ require 'fileutils'
2
+ require 'tmpdir'
3
+
4
+ require 'version50/scm'
5
+
6
+ class Git < SCM
7
+ # commit a new version without pushing
8
+ def commit
9
+ # prompt for commit message
10
+ message = super
11
+
12
+ # add all files and commit
13
+ `git add --all`
14
+ `git commit -m "#{message}"`
15
+ end
16
+
17
+ # configure the repo with user's info
18
+ def config info
19
+ # configure git user
20
+ `git config user.name "#{info['name']}"`
21
+ `git config user.email "#{info['email']}"`
22
+
23
+ # configure remote if not already
24
+ origin = `git remote`
25
+ if origin == '' && info['remote'] != ''
26
+ `git remote add origin #{info['remote']}`
27
+ end
28
+ end
29
+
30
+ # create a new repo
31
+ def init
32
+ `git init`
33
+ `echo ".version50" > .gitignore`
34
+ end
35
+
36
+ # view the project history
37
+ def log
38
+ # great idea or greatest idea?
39
+ delimiter = '!@#%^&*'
40
+ history = `git log --graph --pretty=format:'#{delimiter} %h #{delimiter} %s #{delimiter} %cr #{delimiter} %an' --abbrev-commit`
41
+
42
+ # iterate over history lines
43
+ commits = []
44
+ lines = history.split "\n"
45
+ lines.each_with_index do |line, i|
46
+ # get information from individual commits
47
+ commit = line.split(delimiter).map { |s| s.strip }
48
+ commits.push({
49
+ :id => commit[1],
50
+ :message => commit[2],
51
+ :timestamp => commit[3],
52
+ :author => commit[4]
53
+ })
54
+ end
55
+
56
+ return commits
57
+ end
58
+
59
+ def pull
60
+ end
61
+
62
+ # push existing commits
63
+ def push
64
+ `git push -u origin master > /dev/null 2>&1`
65
+ end
66
+
67
+ # view changed files
68
+ def status
69
+ # get status from SCM
70
+ status = `git status`
71
+
72
+ # iterate over each line in status
73
+ tracked = 0
74
+ added, modified, deleted = [], [], []
75
+ status.split("\n").each do |line|
76
+ # ignore git system lines
77
+ if tracked > 0 && line && line !=~ /\(use "git add <file>\.\.\." to include in what will be committed\)/ &&
78
+ line !=~ /\(use "git add <file>\.\.\." to update what will be committed\)/ &&
79
+ line !=~ /\(use "git checkout -- <file>\.\.\." to discard changes in working directory\)/
80
+
81
+ # untracked files, so mark as added
82
+ if tracked == 1
83
+ # determine filename
84
+ line =~ /^#\s*([\w\/\.\-]+)/
85
+ if $1
86
+ added.push $1
87
+ end
88
+
89
+ # currently-tracked files
90
+ elsif tracked == 2
91
+ # determine filename and modified status
92
+ line =~ /^#\s*([\w]+):\s*([\w\/\.\-]+)/
93
+
94
+ # tracked and modified
95
+ if $1 == 'modified'
96
+ modified.push $2
97
+
98
+ # tracked and deleted
99
+ elsif $1 == 'deleted'
100
+ deleted.push $2
101
+ end
102
+ end
103
+ end
104
+
105
+ # make sure untracked files are marked as added
106
+ if line =~ /Untracked files:/
107
+ tracked = 1
108
+ elsif line =~ /Changes not staged for commit:/ || line =~ /Changes to be committed:/
109
+ tracked = 2
110
+ end
111
+ end
112
+
113
+ return {
114
+ :added => added,
115
+ :deleted => deleted,
116
+ :modified => modified
117
+ }
118
+ end
119
+
120
+ # warp to a specific version
121
+ def warp
122
+ # save current state before doing anything
123
+ revision = super
124
+
125
+ # determine project root and warp destination
126
+ path = @version50.root
127
+ dest = "version50-#{revision[:revision]}"
128
+
129
+ # create temporary directory to clone project into
130
+ Dir.mktmpdir do |d|
131
+ # clone project into temporary directory and revert to given revision
132
+ Dir.chdir(File.expand_path d)
133
+ `git clone #{path} . > /dev/null 2> /dev/null`
134
+ `git checkout #{revision[:id]} -f > /dev/null 2> /dev/null`
135
+
136
+ # switch back to project root and create folder for warp
137
+ Dir.chdir(File.expand_path path)
138
+ FileUtils.mkdir dest
139
+
140
+ # move all files in temporary directory into warp directory
141
+ FileUtils.mv(Dir.glob(File.expand_path(d) + '/*'), dest)
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,72 @@
1
+ require 'pathname'
2
+
3
+ class SCM
4
+ def initialize(version50)
5
+ @version50 = version50
6
+ end
7
+
8
+ # commit changes without pushing
9
+ def commit
10
+ # check if we have anything to commit
11
+ files = self.status
12
+ if files[:added].length == 0 && files[:modified].length == 0 && files[:deleted].length == 0
13
+ puts "Nothing has changed since your last save!"
14
+
15
+ # prompt for commit message
16
+ else
17
+ puts "\033[34mWhat changes have you made since your last save?\033[0m "
18
+ message = $stdin.gets.chomp
19
+ end
20
+
21
+ return message
22
+ end
23
+
24
+ # configure the repo with the user's info
25
+ def config info
26
+ end
27
+
28
+ # initialize a new repo
29
+ def init
30
+ end
31
+
32
+ # view the project history
33
+ def log
34
+ end
35
+
36
+ def pull
37
+ end
38
+
39
+ # push existing commits
40
+ def push
41
+ end
42
+
43
+ # shortcut for commit and push
44
+ def save
45
+ self.commit
46
+ self.push
47
+ end
48
+
49
+ # view changed files
50
+ def status
51
+ end
52
+
53
+ # warp to a specific revision
54
+ def warp
55
+ # save before doing anything
56
+ self.save
57
+
58
+ # prompt for revision
59
+ print "\033[34mWhat version would you like to warp to?\033[0m "
60
+ revision = $stdin.gets.chomp.to_i(10)
61
+
62
+ puts "\033[32mPutting files into version50-#{revision}...\033[0m "
63
+
64
+ # get revision from numerical index
65
+ revisions = self.log
66
+ r = revisions[revisions.length - revision]
67
+
68
+ # add numerical index to return value
69
+ r[:revision] = revision
70
+ return r
71
+ end
72
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: version50
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Tommy MacWilliam
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-12-16 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: json
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: highline
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: A student-friendly SCM abstraction layer.
49
+ email: tmacwilliam@cs.harvard.edu
50
+ executables:
51
+ - version50
52
+ extensions: []
53
+
54
+ extra_rdoc_files: []
55
+
56
+ files:
57
+ - lib/version50.rb
58
+ - lib/version50/git.rb
59
+ - lib/version50/scm.rb
60
+ - bin/version50
61
+ homepage: https://github.com/tmacwill/version50
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options: []
66
+
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ requirements: []
88
+
89
+ rubyforge_project:
90
+ rubygems_version: 1.8.24
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: A student-friendly SCM abstraction layer.
94
+ test_files: []
95
+