scipio 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # Scipio
2
+
3
+ A small CLI utility to:
4
+ - set up new Ruby Gems using a configurable template
5
+ - set up local & remote git repos
6
+ - launch Rake tasks from anywhere
7
+
8
+ Generally to help set up & run Ruby projects with less typing. Scipio. Skip IO. Geddit?
9
+
10
+ ## Status
11
+
12
+ Early days. Development status "3 - Alpha" in Python terms.
13
+
14
+ (Those Python statuses are 1 - Planning, 2 - Pre-alpha, 3 - Alpha, 4 - Beta, 5 - Production/Stable, 6 - Mature, 7 - Inactive.)
15
+
16
+ ## Ancient history
17
+
18
+ <img src="./Scipio.png" alt="Scipio Africanus, OG" />
19
+
20
+ In 2015 Scipio was a Swift package manager written in Python, but it was soon made obsolete by the growth of Apple's own package manager. Carthage was one of the other alternatives around at the time. Carthago delenda est.
21
+
22
+ A little earlier, Scipio was also a Roman general & one of the authors of Carthage's defeat in the Second Punic War.
23
+
24
+ ## Development
25
+
26
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake test` to run the tests.
27
+
28
+ To install this gem locally, run `bundle exec rake local`.
29
+
30
+ `bundle exec rake -T` to see the current options.
31
+
32
+ ## Contributing
33
+
34
+ Bug reports and pull requests are welcome at [https://codeberg.org/kreuzer/scipio](https://codeberg.org/kreuzer/scipio). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://codeberg.org/kreuzer/scipio/src/branch/main/CODE_OF_CONDUCT.md).
35
+
36
+ ## Code of Conduct
37
+
38
+ Everyone interacting in the Scipio project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://codeberg.org/kreuzer/scipio/src/branch/main/CODE_OF_CONDUCT.md).
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'scipio'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start
data/bin/scipio ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
5
+
6
+ require 'bundler/setup'
7
+ require 'scipio/cli'
8
+
9
+ Scipio::CLI.new.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/scipio/cli.rb ADDED
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require "yaml"
5
+
6
+ require_relative "config"
7
+ require_relative "gems"
8
+ require_relative "git"
9
+ require_relative "rake"
10
+
11
+ module Scipio
12
+ # CLI handled by optparse
13
+ class CLI
14
+ def start(*_args)
15
+ @options = {}
16
+ OptionParser.new do |opts|
17
+ opts.banner = <<~BANNER
18
+ Scipio
19
+ Expanded help here
20
+
21
+ Commands:
22
+ BANNER
23
+ gem(opts)
24
+ help(opts)
25
+ init(opts)
26
+ local(opts)
27
+ naked_repo(opts)
28
+ rake(opts)
29
+ task(opts)
30
+ version(opts)
31
+ end.parse!
32
+
33
+ create_gem
34
+ create_repo
35
+ create_task
36
+ rescue OptionParser::InvalidOption
37
+ puts "Invalid option"
38
+ # don't have opts here to show help
39
+ end
40
+
41
+ def gem(opts)
42
+ opts.on("-g GEM", "--gem GEM", "Create a new ruby gem named GEM") do |opt|
43
+ @options[:gem] = opt
44
+ end
45
+ end
46
+
47
+ def help(opts)
48
+ opts.on("-h", "--help", "This help message") do
49
+ puts opts
50
+ exit
51
+ end
52
+ end
53
+
54
+ def init(opts)
55
+ opts.on("-i", "--initialize", "Initialize config & template files, show details if they exist") do
56
+ config = Scipio::Config.new
57
+ config.info
58
+ gem = Scipio::Gems.new(config:, gem_name: "")
59
+ gem.init
60
+ end
61
+ end
62
+
63
+ def local(opts)
64
+ opts.on("-l", "--local", "With a gem or repo, do not create or connect to the origin") do
65
+ @options[:local] = true
66
+ end
67
+ end
68
+
69
+ def naked_repo(opts)
70
+ opts.on("-n REPO", "--naked REPO", "Create a new naked git repo named REPO") do |opt|
71
+ @options[:repo] = opt
72
+ end
73
+ end
74
+
75
+ def rake(opts)
76
+ opts.on("-r RAKE", "--rake RAKE", "Run Rake in directory RAKE, or named RAKE in config") do |opt|
77
+ @options[:rake] = opt
78
+ end
79
+ end
80
+
81
+ def task(opts)
82
+ opts.on("-t TASK", "--task TASK", "Rake task, use -t -T to list tasks, leave off for the default task") do |opt|
83
+ @options[:task] = opt
84
+ end
85
+ end
86
+
87
+ def version(opts)
88
+ opts.on("-v", "--version", "The version") do
89
+ puts Scipio::VERSION
90
+ exit
91
+ end
92
+ end
93
+
94
+ # TODO: provide a path here to override the default
95
+ def create_gem
96
+ if @options[:gem]
97
+ config = Scipio::Config.new
98
+ repo = Scipio::Git.new(config:, repo_name: @options[:gem])
99
+ gem = Scipio::Gems.new(config:, gem_name: @options[:gem])
100
+
101
+ repo.create_dir
102
+ repo.init_before_write
103
+ gem.write
104
+ repo.init_after_write
105
+ unless @options[:local]
106
+ repo.remote
107
+ end
108
+
109
+ exit
110
+ end
111
+ end
112
+
113
+ def create_repo
114
+ if @options[:repo]
115
+ config = Scipio::Config.new
116
+ repo = Scipio::Git.new(config:, repo_name: @options[:repo])
117
+
118
+ repo.create_dir
119
+ repo.naked_write
120
+ unless @options[:local]
121
+ repo.remote
122
+ end
123
+
124
+ exit
125
+ end
126
+ end
127
+
128
+ def create_task
129
+ if @options[:rake]
130
+ config = Scipio::Config.new
131
+ @options[:task] ||= ""
132
+ rake = Scipio::Rake.new(options: @options, config:)
133
+ rake.run
134
+ exit
135
+ end
136
+
137
+ # no rake dir or name, but a task
138
+ if @options[:task]
139
+ puts "--task requires --rake NAME, the directory or named Rake file you're calling this task on"
140
+ exit
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Scipio
6
+ # Stored git repo config for Scipio
7
+ class Config
8
+ attr_reader :git_dir,
9
+ :git_domain,
10
+ :git_site,
11
+ :rake_dirs,
12
+ :user_email,
13
+ :user_fullname,
14
+ :user_name
15
+
16
+ def self.config_dir
17
+ File.join(Config.home_dir, ".config/scipio")
18
+ end
19
+
20
+ def self.config_file
21
+ File.join(Config.config_dir, "config.yml")
22
+ end
23
+
24
+ def self.home_dir
25
+ path = if RUBY_PLATFORM.match?(/cygwin | mswin | mingw | bccwin | wince | emx /)
26
+ "%userprofile%"
27
+ else
28
+ "~"
29
+ end
30
+ File.expand_path(path)
31
+ end
32
+
33
+ def self.template_dir
34
+ File.join(Config.config_dir, "template")
35
+ end
36
+
37
+ def initialize
38
+ if File.exist?(Config.config_file)
39
+ read_file
40
+ else
41
+ init
42
+ end
43
+ end
44
+
45
+ # TODO errors
46
+ # TODO read config values as defaults, with chance to overide interactively
47
+ # rescue Errno::ENOENT => _e
48
+ # puts "No config file present"
49
+ # rescue Psych::SyntaxError => error
50
+ # puts "Could not parse config: #{error.message}"
51
+ # ensure
52
+ # exit
53
+ # end
54
+
55
+ def init
56
+ if File.exist?(Config.config_file)
57
+ return
58
+ end
59
+ ask_for_git_dir
60
+ parse_git_config
61
+ parse_berg_user_info
62
+ parse_berg_config
63
+ @git_site = "#{@git_site}/#{@user_name}"
64
+ FileUtils.mkdir_p(Config.template_dir)
65
+ save_file
66
+ end
67
+
68
+ def info
69
+ puts <<~INFO
70
+ Current config in - #{Config.config_file}
71
+
72
+ Git
73
+ local: \t#{@git_dir}
74
+ domain: #{@git_domain}
75
+ origin: #{@git_site}
76
+ e-mail: #{@user_email}
77
+ name: \t#{@user_fullname}
78
+ user: \t#{@user_name}
79
+
80
+ Rake
81
+ INFO
82
+ if rake_dirs
83
+ rake_dirs.each do |name, dir|
84
+ puts "#{name} - #{dir}"
85
+ end
86
+ else
87
+ puts "No rake directories defined"
88
+ end
89
+ # exit
90
+ end
91
+
92
+ def ask_for_git_dir
93
+ puts "What directory should local git repos be created in?"
94
+ dir = File.expand_path($stdin.gets.chomp.strip)
95
+ raise StandardError.new "#{dir} - doesn't seem to exists, enter manually in file" unless File.directory?(dir)
96
+ rescue => error
97
+ puts error.message
98
+ dir = ""
99
+ ensure
100
+ @git_dir = dir
101
+ end
102
+
103
+ def read_file
104
+ config = YAML.safe_load_file(Config.config_file, permitted_classes: [Hash])
105
+ git = config["git"]
106
+ @git_dir = git["git_dir"]
107
+ @git_domain = git["git_domain"]
108
+ @git_site = git["git_site"]
109
+ @user_email = git["user_email"]
110
+ @user_fullname = git["user_fullname"]
111
+ @user_name = git["user_name"]
112
+ @rake_dirs = config["rake"]
113
+ end
114
+
115
+ def save_file
116
+ config = {"git" => {
117
+ "git_dir" => @git_dir,
118
+ "git_domain" => @git_domain,
119
+ "git_site" => @git_site,
120
+ "user_email" => @user_email,
121
+ "user_fullname" => @user_fullname,
122
+ "user_name" => @user_name
123
+ },
124
+ "rake" => {
125
+ "name" => "path"
126
+ }}
127
+ json = config.to_yaml
128
+ File.write(Config.config_file, json)
129
+ end
130
+
131
+ # ~/.Config -> @user_email, @user_fullname
132
+ def parse_git_config
133
+ git_config = File.join(Config.home_dir, ".gitconfig")
134
+ File.readlines(git_config, chomp: true).each do |line|
135
+ res = line.match(/email = (?<email>\S+)/)
136
+ unless res.nil?
137
+ @user_email = res[:email]
138
+ end
139
+ res = line.match(/name = (?<name>.+)$/)
140
+ unless res.nil?
141
+ @user_fullname = res[:name]
142
+ end
143
+ end
144
+ end
145
+
146
+ # berg user info -> @user_name
147
+ def parse_berg_user_info
148
+ bash = `berg user info`
149
+ res = bash.match(/│ Username\s+┆ (?<name>\S+)\s+│/)
150
+ @user_name = res[:name]
151
+ # TODO: could not get git user name from berg, please enter manually - #{Config.config_file}
152
+ end
153
+
154
+ # berg config info -> @git_site
155
+ def parse_berg_config
156
+ # ~/Library/Application Support/berg-cli/berg.toml -> git_site
157
+ # but would be OS specific
158
+ # berg_config = File.join(home_dir, "Library/Application Support/berg-cli/berg.toml")
159
+ # url = ""
160
+ # protocol = ""
161
+ # File.readlines(berg_config, chomp: true).each do |line|
162
+ # res = line.match(/base_url = (?<url>\S+)/)
163
+ # unless res.nil?
164
+ # url = res[:url].tr("\"", "")
165
+ # end
166
+ # res = line.match(/protocol = (?<protocol>.+)$/)
167
+ # unless res.nil?
168
+ # protocol = res[:protocol].tr("\"", "")
169
+ # end
170
+ # end
171
+ # @git_site = "#{protocol}://#{url}"
172
+ bash = `berg config info`
173
+ protocol = bash.match(/│ protocol\s+┆ ".+" ┆ (?<protocol>\S+)\s+│/)
174
+ url = bash.match(/│ base_url\s+┆ ".+" ┆ (?<url>\S+)\s+│/)
175
+ @git_domain = url[:url].tr("\"", "").tr("/", "")
176
+ @git_site = "#{protocol[:protocol].tr("\"", "")}://#{@git_domain}"
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "erb"
5
+ require "fileutils"
6
+ require "pathname"
7
+
8
+ require_relative "config"
9
+
10
+ module Scipio
11
+ # Create the skeleton of a new Gem
12
+ class Gems
13
+ def initialize(config:, gem_name:)
14
+ @gem_default_templates = File.expand_path("../../template", File.dirname(__FILE__))
15
+
16
+ @config_dir = Config.config_dir
17
+ @gem_name = gem_name
18
+ @git_dir = config.git_dir
19
+ @template_dir = Config.template_dir
20
+ @vars = {gem_name: @gem_name,
21
+ git_site: config.git_site,
22
+ user_name: config.user_name,
23
+ user_fullname: config.user_fullname,
24
+ user_email: config.user_email,
25
+ date: DateTime.now}
26
+ end
27
+
28
+ def templates(dir:)
29
+ # except for dot files this could be... Dir[File.join(dir, "**/*")]
30
+ filter_types = %w[. .. .DS_Store]
31
+ Dir.glob(File.join(dir, "**/*"), File::FNM_DOTMATCH)
32
+ .select { |file| !file.end_with?(*filter_types) }
33
+ end
34
+
35
+ def relative_out_path(path_from:)
36
+ out = Pathname.new(path_from)
37
+ .relative_path_from(@template_dir)
38
+ .to_s
39
+ .chomp(".t")
40
+ out % @vars
41
+ end
42
+
43
+ def write
44
+ out_dir = File.join(@git_dir, @gem_name)
45
+ # create_out_dir(out_dir:)
46
+ templates(dir: @template_dir).each do |path_from|
47
+ path_to = File.join(out_dir, relative_out_path(path_from:))
48
+
49
+ if File.directory? path_from
50
+ FileUtils.mkdir_p path_to
51
+ else
52
+ puts path_to # TODO: unless silent
53
+ template = ERB.new(File.read(path_from))
54
+ IO.write(path_to, template.result_with_hash(@vars))
55
+ end
56
+ end
57
+ end
58
+
59
+ # copy the default template files for users to modify or use
60
+ def init
61
+ FileUtils.mkdir_p @template_dir
62
+ unless Dir.empty? @template_dir
63
+ puts "Templates directory not empty - #{@template_dir}"
64
+ exit
65
+ end
66
+ FileUtils.cp_r(@gem_default_templates, @config_dir)
67
+ end
68
+ end
69
+ end
data/lib/scipio/git.rb ADDED
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scipio
4
+ class Git
5
+ def initialize(config:, repo_name:)
6
+ @git_domain = config.git_domain
7
+ @repo_name = repo_name
8
+ @user_name = config.user_name
9
+
10
+ @dir = File.join(config.git_dir, @repo_name)
11
+ # create_dir
12
+ ensure_main
13
+ end
14
+
15
+ def create_dir
16
+ if File.exist?(@dir)
17
+ puts "Directory already exists - #{@dir}"
18
+ exit
19
+ end
20
+ FileUtils.mkdir_p(@dir)
21
+ end
22
+
23
+ # TODO: call out changing git setup, and/or push name to config
24
+ def ensure_main
25
+ system "git config --global init.defaultBranch main"
26
+ end
27
+
28
+ def init_before_write
29
+ Dir.chdir(@dir) do
30
+ system "git init"
31
+ end
32
+ end
33
+
34
+ # alternative to writing out gem files
35
+ def init_write
36
+ Dir.chdir(@dir) do
37
+ system "touch README.md"
38
+ system "touch .gitignore"
39
+ end
40
+ end
41
+
42
+ def init_after_write
43
+ Dir.chdir(@dir) do
44
+ system "git checkout -b main"
45
+ system "git add ."
46
+ system 'git commit -m "Initial commit"'
47
+ end
48
+ end
49
+
50
+ def naked_write
51
+ init_before_write
52
+ init_write
53
+ init_after_write
54
+ end
55
+
56
+ # TODO - alt for github, etc
57
+ # TODO - ssh/sftp choice
58
+ #
59
+ # fatal: protocol 'git@https' is not supported
60
+ def remote
61
+ Dir.chdir(@dir) do
62
+ system "berg repo create --default-branch main --description 'To follow' --name #{@repo_name} --private public"
63
+ system "git remote add origin git@#{@git_domain}:#{@user_name}/#{@repo_name}.git"
64
+ system "git push -u origin main"
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,45 @@
1
+ require "bundler"
2
+
3
+ module Scipio
4
+ # To call Rake from anywhere
5
+ class Rake
6
+ def initialize(options:, config:)
7
+ @rake = options[:rake]
8
+ @task = options[:task] # may be an empty string
9
+ @dir = config.rake_dirs
10
+ end
11
+
12
+ # if running without bundler
13
+ # https://mattbrictson.com/blog/run-shell-commands-in-ruby
14
+ def self.with_original_bundler_env(&)
15
+ return yield unless defined?(Bundler)
16
+ Bundler.with_original_env(&)
17
+ end
18
+
19
+ def run
20
+ dir = try_dir(rake: @rake)
21
+ Dir.chdir(dir) do
22
+ # Bundler.with_original_env do
23
+ Rake.with_original_bundler_env do
24
+ system("bundle exec rake #{@task} --trace", exception: true)
25
+ end
26
+ end
27
+ end
28
+
29
+ # try for a directory, then an entry in the config
30
+ # TODO: note - watch out for directories in the directory you're calling scipio from,
31
+ # rake will search the tree upwards from any directory it uses
32
+ def try_dir(rake:)
33
+ dir =
34
+ if File.directory?(File.expand_path(rake))
35
+ rake
36
+ elsif @dir.key?(rake) && File.directory?(File.expand_path(@dir[rake]))
37
+ @dir[rake]
38
+ else
39
+ p "Don't know directory or config entry - #{rake}"
40
+ exit
41
+ end
42
+ File.expand_path(dir)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scipio
4
+ VERSION = "0.6.0"
5
+ end
data/lib/scipio.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "scipio/version"
4
+
5
+ # Scipio's top level module
6
+ module Scipio
7
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # probably no longer required
4
+ # lib = File.expand_path("lib", __dir__)
5
+ # $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+
7
+ require_relative "lib/<%= gem_name %>/version"
8
+
9
+ Gem::Specification.new do |spec|
10
+ spec.name = "<%= gem_name %>"
11
+ spec.version = <%= gem_name.capitalize %>::VERSION
12
+ spec.authors = ["<%= user_fullname %>"]
13
+ spec.email = ["<%= user_email %>"]
14
+
15
+ spec.summary = "TODO: Description"
16
+ spec.description = <<~DESC
17
+ TODO: Longer description
18
+ DESC
19
+ spec.homepage = "<%= git_site %>/<%= user_name %>/<%= gem_name %>"
20
+ spec.license = "AGPL-3.0-or-later"
21
+ spec.required_ruby_version = ">= 3.3"
22
+
23
+ spec.metadata["allowed_push_host"] = "TODO: Set to your gem server or delete line"
24
+
25
+ spec.metadata = {
26
+ "changelog_uri" => "#{spec.homepage}/src/branch/main/CHANGELOG.md",
27
+ "homepage_uri" => spec.homepage,
28
+ "rubygems_mfa_required" => "true",
29
+ "source_code_uri" => spec.homepage
30
+ }
31
+
32
+ spec.files = Dir.glob("{bin,lib,template}/**/*") + %w[LICENSE.txt README.md CHANGELOG.md]
33
+ spec.bindir = "bin"
34
+ spec.executables = "<%= gem_name %>"
35
+ spec.require_paths = ["lib"]
36
+
37
+ unless ENV.fetch("UNSIGNED_BUILD", "false") == "true"
38
+ spec.cert_chain = ["certs/<%= user_name %>.pem"]
39
+ spec.signing_key = File.expand_path("~/.ssh/gem-private_key.pem")
40
+ end
41
+
42
+ # spec.add_dependency "example-gem", "~> 1.0"
43
+ end
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - <%= date.strftime("%e %B %Y") %>
4
+
5
+ - Initial release