├── LICENSE.md ├── NOTES.md ├── README.md ├── commands-lite ├── .gitignore ├── CHANGELOG.md ├── Manifest.txt ├── NOTES.md ├── README.md ├── Rakefile ├── lib │ ├── commands-lite.rb │ └── commands-lite │ │ ├── base.rb │ │ └── version.rb └── sandbox │ └── test_run.rb ├── docs └── backup-your-github-repos.md ├── flow-lite ├── .gitignore ├── CHANGELOG.md ├── Manifest.txt ├── NOTES.md ├── README.md ├── Rakefile ├── bin │ └── flow ├── lib │ ├── flow-lite.rb │ └── flow-lite │ │ ├── base.rb │ │ ├── tool.rb │ │ └── version.rb ├── make │ ├── Makefile │ └── README.md └── sandbox │ ├── Flowfile │ ├── Flowfile2 │ ├── test_run.rb │ └── test_tool.rb ├── flowfile ├── .gitignore ├── CHANGELOG.md ├── Manifest.txt ├── README.md ├── Rakefile └── lib │ └── flowfile.rb ├── gemverse ├── .gitignore ├── CHANGELOG.md ├── Manifest.txt ├── NOTES.md ├── README.md ├── Rakefile ├── lib │ ├── gemverse.rb │ └── gemverse │ │ ├── api.rb │ │ ├── cache.rb │ │ ├── gems.rb │ │ └── timeline.rb └── sandbox │ ├── generate_cocos.rb │ ├── generate_vienna.rb │ ├── generate_yo.rb │ ├── test_api.rb │ ├── timeline_step1.rb │ ├── timeline_step2.rb │ ├── update_gems.rb │ └── versions.rb ├── gitfile ├── .gitignore ├── CHANGELOG.md ├── Manifest.txt ├── NOTES.md ├── README.md ├── Rakefile └── lib │ └── gitfile.rb ├── gitti-backup ├── .gitignore ├── CHANGELOG.md ├── Manifest.txt ├── NOTES.md ├── README.md ├── Rakefile ├── bin │ └── backup ├── lib │ └── gitti │ │ ├── backup.rb │ │ └── backup │ │ ├── backup.rb │ │ ├── base.rb │ │ └── version.rb └── test │ ├── data │ └── repos.yml │ ├── helper.rb │ └── test_backup.rb ├── gitti ├── .gitignore ├── CHANGELOG.md ├── Manifest.txt ├── README.md ├── Rakefile ├── lib │ ├── gitti.rb │ └── gitti │ │ ├── base.rb │ │ ├── git.rb │ │ ├── mirror.rb │ │ ├── project.rb │ │ ├── reposet.rb │ │ └── version.rb ├── sandbox │ └── test_version.rb └── test │ ├── helper.rb │ └── test_base.rb ├── hubba-reports ├── .gitignore ├── CHANGELOG.md ├── Manifest.txt ├── README.md ├── Rakefile ├── lib │ └── hubba │ │ ├── reports.rb │ │ └── reports │ │ ├── folio.rb │ │ ├── reports │ │ ├── base.rb │ │ ├── catalog.rb │ │ ├── languages.rb │ │ ├── size.rb │ │ ├── stars.rb │ │ ├── summary.rb │ │ ├── timeline.rb │ │ ├── topics.rb │ │ ├── traffic.rb │ │ ├── traffic_pages.rb │ │ ├── traffic_referrers.rb │ │ ├── trending.rb │ │ └── updates.rb │ │ ├── stats.rb │ │ └── version.rb └── test │ ├── helper.rb │ ├── stats │ ├── j │ │ └── jekyll~minima.json │ ├── o │ │ ├── openblockchains~awesome-blockchains.json │ │ └── opendatajson~factbook.json.json │ └── p │ │ └── poole~hyde.json │ ├── test_stats.rb │ └── test_stats_tmp.rb ├── hubba ├── .gitignore ├── CHANGELOG.md ├── Manifest.txt ├── NOTES.md ├── README.md ├── Rakefile ├── attic │ ├── cache.rb │ ├── cache │ │ ├── users~geraldb~orgs.json │ │ └── users~geraldb~repos.json │ ├── github.rb │ └── test_cache.rb ├── lib │ ├── hubba.rb │ └── hubba │ │ ├── config.rb │ │ ├── github.rb │ │ ├── reposet.rb │ │ ├── stats.rb │ │ ├── update.rb │ │ ├── update_traffic.rb │ │ └── version.rb ├── sandbox │ ├── test.rb │ ├── test_orgs.rb │ └── test_stats.rb └── test │ ├── helper.rb │ └── test_config.rb ├── monofile ├── .gitignore ├── CHANGELOG.md ├── Manifest.txt ├── README.md ├── Rakefile ├── bin │ └── monofile ├── lib │ ├── monofile.rb │ └── monofile │ │ ├── monofile.rb │ │ ├── mononame.rb │ │ ├── tool.rb │ │ └── version.rb ├── sandbox │ ├── monofile.yml │ ├── test_load.rb │ └── test_read.rb └── test │ ├── helper.rb │ └── test_names.rb ├── monos ├── .gitignore ├── CHANGELOG.md ├── Manifest.txt ├── NOTES.md ├── README.md ├── Rakefile ├── bin │ ├── mo │ └── mono ├── lib │ ├── mono.rb │ ├── mono │ │ ├── base.rb │ │ ├── commands │ │ │ ├── backup.rb │ │ │ ├── env.rb │ │ │ ├── fetch.rb │ │ │ ├── run.rb │ │ │ ├── status.rb │ │ │ └── sync.rb │ │ ├── experimental.rb │ │ ├── tool.rb │ │ └── version.rb │ └── monos.rb ├── sandbox │ ├── test_misc.rb │ └── test_walk.rb └── test │ ├── helper.rb │ ├── test_base.rb │ └── test_path.rb └── yorobot ├── .gitignore ├── CHANGELOG.md ├── Manifest.txt ├── README.md ├── Rakefile ├── attic ├── echo.rb ├── git.rb ├── github.rb └── list.rb ├── bin ├── yo └── yorobot ├── lib ├── yorobot.rb └── yorobot │ └── version.rb └── sandbox ├── Flowfile └── test_run.rb /NOTES.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | ## Git 4 | 5 | - libgit2 bindings in Ruby, see 6 | 7 | 8 | ## GitHub API 9 | 10 | - see - (official) Ruby toolkit for the GitHub API 11 | 12 | 13 | 14 | - see 15 | - see 16 | - add a Mashie like Resource? why? why not? 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git (and github) & monorepo / mono source tree command line tools, libraries & scripts 2 | 3 | Git Gems: 4 | 5 | - [**gitti**](gitti) - (lite) git command line helper / wrapper 6 | - [gitti-backup](gitti-backup) - (lite) multi repo git backup command line script 7 | 8 | _GitHub_ 9 | 10 | 11 | - [hubba](hubba) - (yet) another (lite) GitHub HTTP API client / library 12 | - [hubba-reports](hubba-reports)- auto-generate github statistics / analytics reports from github api data (stars, timeline, traffic, top pages, top referrers, etc.) 13 | 14 | _Monorepos_ 15 | 16 | 17 | - [**monos**](monos) - monorepo / mono source tree tools and scripts 18 | - [monofile](monofile) - read in / parse monorepo / mono source tree definitions - a list of git (and github) projects, and more 19 | 20 | 21 | _Worflow_ 22 | 23 | 24 | - [**flow-lite**](flow-lite) - (yet) another (lite) workflow engine; let's you define your workflow steps in Flowfiles; incl. the flow command line tool 25 | - [**flowfile**](flowfile) - read in / parse (work)flow definitions with steps, actions, and more 26 | - [yorobot](yorobot) - yo, robot - automate, automate, automate - ready to use scripts and command line tool 27 | 28 | 29 | 30 | Gem Universe Gems: 31 | 32 | - [**gemverse**](gemverse) - gem universe incl. rubygems API V1 wrapper lite; gem version cache, gem timeline reports, 'n' more 33 | 34 | 35 | 36 | ## How-Tos & Step-By-Step Guides 37 | 38 | [**How-To Back Up Your GitHub Repos - A Step-By-Step Guide**](https://github.com/rubycocos/git/blob/master/docs/backup-your-github-repos.md) 39 | 40 | 41 | 42 | 43 | ## License 44 | 45 | The `gitti & friends` scripts are dedicated to the public domain. 46 | Use it as you please with no restrictions whatsoever. 47 | 48 | -------------------------------------------------------------------------------- /commands-lite/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /vendor/bundle 26 | /lib/bundler/man/ 27 | 28 | # for a library or gem, you might want to ignore these files since the code is 29 | # intended to run in multiple environments; otherwise, check them in: 30 | # Gemfile.lock 31 | # .ruby-version 32 | # .ruby-gemset 33 | 34 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 35 | .rvmrc 36 | 37 | 38 | -------------------------------------------------------------------------------- /commands-lite/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.1 / 2020-10-22 2 | 3 | * Everything is new. First release. 4 | 5 | -------------------------------------------------------------------------------- /commands-lite/Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | Manifest.txt 3 | README.md 4 | Rakefile 5 | lib/commands-lite.rb 6 | lib/commands-lite/base.rb 7 | lib/commands-lite/version.rb 8 | -------------------------------------------------------------------------------- /commands-lite/NOTES.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | 4 | 5 | ## Ideas 6 | 7 | - [ ] add "shortcut why? why not?" 8 | 9 | ``` 10 | command [:list, :ls] do |args| 11 | ... 12 | end 13 | ``` -------------------------------------------------------------------------------- /commands-lite/README.md: -------------------------------------------------------------------------------- 1 | # commands-lite 2 | 3 | commands-lite gem - (lite) commands 'n' options helper / wrapper / parser / runner 4 | 5 | 6 | * home :: [github.com/rubycoco/git](https://github.com/rubycoco/git) 7 | * bugs :: [github.com/rubycoco/git/issues](https://github.com/rubycoco/git/issues) 8 | * gem :: [rubygems.org/gems/commands-lite](https://rubygems.org/gems/commands-lite) 9 | * rdoc :: [rubydoc.info/gems/commands-lite](http://rubydoc.info/gems/commands-lite) 10 | 11 | 12 | 13 | ## Usage 14 | 15 | To be done 16 | 17 | 18 | 19 | 20 | ## License 21 | 22 | The `commands-lite` scripts are dedicated to the public domain. 23 | Use it as you please with no restrictions whatsoever. 24 | 25 | -------------------------------------------------------------------------------- /commands-lite/Rakefile: -------------------------------------------------------------------------------- 1 | require 'hoe' 2 | require './lib/commands-lite/version.rb' 3 | 4 | Hoe.spec 'commands-lite' do 5 | 6 | self.version = CommandsLite::VERSION 7 | 8 | self.summary = "commands-lite gem - (lite) commands 'n' options helper / wrapper / parser / runner" 9 | self.description = summary 10 | 11 | self.urls = { home: 'https://github.com/rubycoco/git' } 12 | 13 | self.author = 'Gerald Bauer' 14 | self.email = 'ruby-talk@ruby-lang.org' 15 | 16 | # switch extension to .markdown for gihub formatting 17 | self.readme_file = 'README.md' 18 | self.history_file = 'CHANGELOG.md' 19 | 20 | self.extra_deps = [] 21 | 22 | self.licenses = ['Public Domain'] 23 | 24 | self.spec_extras = { 25 | required_ruby_version: '>= 2.2.2' 26 | } 27 | 28 | end 29 | -------------------------------------------------------------------------------- /commands-lite/lib/commands-lite.rb: -------------------------------------------------------------------------------- 1 | require 'pp' 2 | require 'time' 3 | require 'date' ## e.g. Date.today etc. 4 | require 'yaml' 5 | require 'json' 6 | require 'fileutils' ## e.g. FileUtils.mkdir_p etc. 7 | require 'optparse' 8 | 9 | 10 | 11 | ##################### 12 | # our own code 13 | require 'commands-lite/version' # note: let version always go first 14 | require 'commands-lite/base' 15 | 16 | 17 | 18 | # say hello 19 | puts CommandsLite.banner 20 | -------------------------------------------------------------------------------- /commands-lite/lib/commands-lite/base.rb: -------------------------------------------------------------------------------- 1 | class Command 2 | 3 | def self.option_defs 4 | @option_defs ||= {} 5 | end 6 | 7 | def self.option( key, *args ) 8 | option_defs[ key ] = args 9 | end 10 | 11 | 12 | 13 | def options 14 | @options ||= {} 15 | end 16 | 17 | def parse!( args ) 18 | ### todo/check - cache option parser!!!! - why? why not? 19 | OptionParser.new do |parser| 20 | ## add default banner - overwrite if needed/to customize 21 | parser.banner = < (#{command.name}) #{args.join('·')}" 41 | 42 | ## check for options 43 | command.parse!( args ) 44 | 45 | puts " #{command.options.size} opt(s): #{command.options.pretty_inspect}" 46 | puts " #{args.size} arg(s):" 47 | args.each_with_index do |arg,i| 48 | puts " #{[i]} >#{arg}<" 49 | end 50 | 51 | 52 | if args.size > 0 53 | ## todo/check: check/verify arity of run - why? why not? 54 | command.call( *args ) ## use run - why? why not? 55 | else 56 | command.call 57 | end 58 | end 59 | 60 | 61 | def self.command_name 62 | ## note: cut-off leading Yorobot:: for now in class name!!! 63 | ## note: always remove _ for now too!!! 64 | ## note: do NOT use @@name!!! - one instance variable per class needed!! 65 | @name ||= self.name.downcase 66 | .sub( /^yorobot::/, '' ) ## todo/fix: make "exclude" list configure-able why? why not? 67 | .gsub( /[_-]/, '' ) 68 | @name 69 | end 70 | 71 | def name() self.class.command_name; end 72 | 73 | 74 | 75 | 76 | 77 | def self.inherited( klass ) 78 | # puts klass.class.name #=> Class 79 | ## auto-register commands for now - why? why not? 80 | Commands.register( klass ) 81 | end 82 | 83 | end # class Command 84 | 85 | 86 | ############################ 87 | # Commands/Commander registry 88 | class Commands ## todo/check: use Commander/Command{Index,Registry,...} or such - why? why not? 89 | 90 | def self.commands ## todo/check: change to registry or such - why? why not? 91 | @@register ||= {} 92 | end 93 | 94 | def self.register( klass ) 95 | raise ArgumentError, "class MUST be a Command" unless klass.ancestors.include?( Command ) 96 | 97 | h = commands 98 | h[ klass.command_name] = klass 99 | h 100 | end 101 | 102 | 103 | def self.run( args=[] ) 104 | command_name = args.shift 105 | 106 | ## 1) downcase e.g. GithubStats 107 | ## 2) remove - to _ ## treat them the same e.g. github-stats => github_stats 108 | command_name = command_name 109 | .gsub( /[_-]/, '' ) 110 | .downcase 111 | 112 | command = commands[ command_name ] 113 | if command.nil? 114 | puts "!! ERROR: no command definition found for >#{command_name}<; known commands include:" 115 | pp commands 116 | exit 1 117 | end 118 | 119 | command.run( args ) 120 | end 121 | 122 | end # class Commands 123 | 124 | 125 | ########################### 126 | ## add a alias / alternative name - why? why not 127 | Commander = Commands 128 | 129 | 130 | -------------------------------------------------------------------------------- /commands-lite/lib/commands-lite/version.rb: -------------------------------------------------------------------------------- 1 | module CommandsLite 2 | 3 | MAJOR = 0 ## todo: namespace inside version or something - why? why not?? 4 | MINOR = 1 5 | PATCH = 0 6 | VERSION = [MAJOR,MINOR,PATCH].join('.') 7 | 8 | def self.version 9 | VERSION 10 | end 11 | 12 | def self.banner 13 | "commands-lite/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" 14 | end 15 | 16 | def self.root 17 | File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) ) 18 | end 19 | 20 | end # module CommandsLite 21 | -------------------------------------------------------------------------------- /commands-lite/sandbox/test_run.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift( "./lib" ) 2 | require 'commands-lite' 3 | 4 | 5 | class Echo < Command 6 | 7 | def call( *args ) 8 | puts args.join( ' ' ) 9 | end 10 | 11 | end # class Echo 12 | 13 | 14 | 15 | class Hello < Command 16 | option :from, '--from NAME' 17 | option :yell, '--yell' 18 | 19 | def call( name ) 20 | output = [] 21 | output << "from: #{options[:from]}" if options[:from] 22 | output << "Hello #{name}" 23 | output = output.join("\n") 24 | puts options[:yell] ? output.upcase : output 25 | end 26 | end 27 | 28 | 29 | pp Commands.commands 30 | pp Commander.commands 31 | 32 | Commands.run( ['echo', 'Hello,', 'World!'] ) 33 | 34 | 35 | Commands.run( ['hello', 'Carola Lerche', '--from', 'Max Katz'] ) 36 | Commands.run( ['hello', '--from', 'Max Katz', 'Carola Lerche'] ) 37 | Commands.run( ['hello', '--from=Max_Katz', 'Carola Lerche'] ) 38 | Commands.run( ['hello', 'Carola Lerche'] ) 39 | Commands.run( ['hello', '--yell', '--from=Max_Katz', 'Carola Lerche'] ) 40 | Commands.run( ['hello', '--yell', 'Carola Lerche'] ) 41 | 42 | 43 | Hello.run( ['Carola Lerche', '--from', 'Max Katz'] ) 44 | Hello.run( ['--from', 'Max Katz', 'Carola Lerche'] ) 45 | Hello.run( ['Carola Lerche'] ) 46 | Hello.run( ['--yell', '--from', 'Max Katz', 'Carola Lerche'] ) 47 | Hello.run( ['--yell', 'Carola Lerche'] ) 48 | 49 | 50 | 51 | ## todo/fix: check how to handle/construct ARGV: 52 | ## --from="Max Katz" !!!!! 53 | ## Commands.run( ['hello', '--from="Max Katz"', 'Carola Lerche'] ) 54 | -------------------------------------------------------------------------------- /flow-lite/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /vendor/bundle 26 | /lib/bundler/man/ 27 | 28 | # for a library or gem, you might want to ignore these files since the code is 29 | # intended to run in multiple environments; otherwise, check them in: 30 | # Gemfile.lock 31 | # .ruby-version 32 | # .ruby-gemset 33 | 34 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 35 | .rvmrc 36 | 37 | 38 | -------------------------------------------------------------------------------- /flow-lite/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.1 / 2020-10-23 2 | 3 | * Everything is new. First release. 4 | 5 | -------------------------------------------------------------------------------- /flow-lite/Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | Manifest.txt 3 | README.md 4 | Rakefile 5 | bin/flow 6 | lib/flow-lite.rb 7 | lib/flow-lite/base.rb 8 | lib/flow-lite/tool.rb 9 | lib/flow-lite/version.rb 10 | -------------------------------------------------------------------------------- /flow-lite/NOTES.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | ## Todos 4 | 5 | - [ ] use "internal" `__send__ ` method version and NOT `send ` 6 | 7 | - [ ] check for more auto-load names by convention e.g. like rake - why? why not? 8 | - No Rakefile found (looking for: rakefile, Rakefile, rakefile.rb, Rakefile.rb) 9 | - e.g. flowfile, Flowfile, flowfile.rb, Flowfile.rb, etc. 10 | 11 | -------------------------------------------------------------------------------- /flow-lite/Rakefile: -------------------------------------------------------------------------------- 1 | require 'hoe' 2 | require './lib/flow-lite/version.rb' 3 | 4 | Hoe.spec 'flow-lite' do 5 | 6 | self.version = FlowLite::VERSION 7 | 8 | self.summary = "flow-lite gem - (yet) another (lite) workflow engine; let's you define your workflow steps in Flowfiles; incl. the flow command line tool" 9 | self.description = summary 10 | 11 | self.urls = { home: 'https://github.com/rubycoco/flow' } 12 | 13 | self.author = 'Gerald Bauer' 14 | self.email = 'ruby-talk@ruby-lang.org' 15 | 16 | # switch extension to .markdown for gihub formatting 17 | self.readme_file = 'README.md' 18 | self.history_file = 'CHANGELOG.md' 19 | 20 | self.extra_deps = [] 21 | 22 | self.licenses = ['Public Domain'] 23 | 24 | self.spec_extras = { 25 | required_ruby_version: '>= 2.2.2' 26 | } 27 | end 28 | -------------------------------------------------------------------------------- /flow-lite/bin/flow: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ################### 4 | # DEV TIPS: 5 | # 6 | # For local testing run like: 7 | # 8 | # ruby -Ilib bin/flow 9 | # 10 | # Set the executable bit in Linux. Example: 11 | # 12 | # % chmod a+x bin/flow 13 | # 14 | 15 | require 'flow-lite' 16 | 17 | Flow::Tool.main 18 | -------------------------------------------------------------------------------- /flow-lite/lib/flow-lite.rb: -------------------------------------------------------------------------------- 1 | ## 2 | ## "prelude / prolog " add some common used stdlibs 3 | ## add more - why? why not? 4 | require 'pp' 5 | require 'time' 6 | require 'date' 7 | require 'json' 8 | require 'yaml' 9 | require 'fileutils' 10 | 11 | require 'uri' 12 | require 'net/http' 13 | require 'net/https' 14 | 15 | 16 | require 'optparse' 17 | 18 | 19 | 20 | ##################### 21 | # our own code 22 | require 'flow-lite/version' # note: let version always go first 23 | require 'flow-lite/base' 24 | require 'flow-lite/tool' 25 | 26 | 27 | 28 | 29 | module Flow 30 | class Step 31 | attr_reader :names # e.g. :list or [:list,:ls] etc. 32 | attr_reader :block 33 | 34 | def name() @names[0]; end ## "primary" name 35 | 36 | def initialize( name_or_names, block ) 37 | @names = if name_or_names.is_a?( Array ) 38 | name_or_names 39 | else 40 | [name_or_names] ## assume single symbol (name); wrap in array 41 | end 42 | @names = @names.map {|name| name.to_sym } ## make sure we always use symbols 43 | @block = block 44 | end 45 | end # class Step 46 | 47 | 48 | class Flowfile 49 | 50 | ## find flowfile path by convention 51 | ## check for name by convention in this order: 52 | NAMES = ['flowfile', 'Flowfile', 53 | 'flowfile.rb', 'Flowfile.rb'] 54 | def self.find_file 55 | NAMES.each do |name| 56 | return "./#{name}" if File.exist?( "./#{name}" ) 57 | end 58 | nil 59 | end # method self.find_file 60 | 61 | 62 | ## convenience method - use like Flowfile.load_file() 63 | def self.load_file( path ) 64 | code = File.open( path, 'r:utf-8' ) { |f| f.read } 65 | load( code ) 66 | end 67 | 68 | ## another convenience method - use like Flowfile.load() 69 | def self.load( code ) 70 | flowfile = new 71 | flowfile.instance_eval( code ) 72 | flowfile 73 | end 74 | 75 | 76 | 77 | def flow 78 | ## todo/check: always return a new instance why? why not? 79 | flow_class.new 80 | end 81 | 82 | def flow_class 83 | @flow_class ||= build_flow_class 84 | end 85 | 86 | def build_flow_class 87 | puts "[flow] building flow class..." 88 | klass = Class.new( Base ) 89 | 90 | steps.each do |step| 91 | klass.define_step( step.names, &step.block ) 92 | end 93 | 94 | klass 95 | end 96 | 97 | 98 | 99 | def initialize( opts={} ) 100 | @opts = opts 101 | @steps = [] 102 | end 103 | 104 | attr_reader :steps 105 | 106 | ## "classic / basic" primitives - step 107 | def step( name, &block ) 108 | @steps << Step.new( name, block ) 109 | end 110 | 111 | def run( name ) 112 | ## todo/check: always return/use a new instance why? why not? 113 | flow_class.new.step( name ) 114 | end 115 | end # class Flowfile 116 | end # module Flow 117 | 118 | 119 | 120 | # say hello 121 | puts FlowLite.banner 122 | 123 | 124 | -------------------------------------------------------------------------------- /flow-lite/lib/flow-lite/base.rb: -------------------------------------------------------------------------------- 1 | 2 | module Flow 3 | 4 | class Base ## base class for flow class (auto)-constructed/build from flowfile 5 | def self.define_step( name_or_names, &block ) 6 | names = if name_or_names.is_a?( Array ) 7 | name_or_names 8 | else 9 | [name_or_names] ## assume single symbol (name); wrap in array 10 | end 11 | names = names.map {|name| name.to_sym } ## make sure we always use symbols 12 | 13 | 14 | name = names[0] 15 | puts "[flow] adding step >#{name}<..." 16 | define_method( :"step_#{name}", &block ) 17 | 18 | alt_names = names[1..-1] 19 | alt_names.each do |alt_name| 20 | puts "[flow] adding alias >#{alt_name}< for >#{name}<..." 21 | alias_method( :"step_#{alt_name}", :"step_#{name}" ) 22 | end 23 | end # method self.define_step 24 | 25 | 26 | 27 | 28 | TRUE_VALUES = [ 29 | 'true', 't', 30 | 'yes', 'y', 31 | 'on', 32 | '1', 33 | ] 34 | 35 | ### include / check for ruby debug flag too - why? why not? 36 | def debug? 37 | value = ENV['DEBUG'] 38 | if value && TRUE_VALUES.include?( value.downcase ) 39 | true 40 | else 41 | false 42 | end 43 | end 44 | 45 | 46 | 47 | ## run step by symbol/name (e.g. step :hello - etc.) 48 | ## 49 | ## todo/check: allow (re)entrant calls to step (step calling step etc.) - why? why not? 50 | def step( name ) 51 | step_name = :"step_#{name}" ## note: make sure we always use symbols 52 | if respond_to?( step_name ) 53 | ####### 54 | ## check: track (and report) call stack - why? why not? 55 | ## e.g. 56 | ## [flow >(1) first_step)] step >first_step< - starting... 57 | ## [flow >(2) ..first_step > second_step)] step >second_step< - starting... 58 | ## [flow >(3) ....first_step > second_step > third_step)] step >third_step< - starting... 59 | @stack ||= [] ## use a call stack of step names 60 | @stack.push( name ) 61 | 62 | puts "[flow >(#{@stack.size}) #{'..'*(@stack.size-1)}#{@stack.join(' > ')})] step >#{name}< - starting..." 63 | start_time = Time.now ## todo: use Timer? t = Timer.start / stop / diff etc. - why? why not? 64 | 65 | __send__( step_name ) 66 | 67 | end_time = Time.now 68 | diff_time = end_time - start_time 69 | puts "[flow <(#{@stack.size}) #{'..'*(@stack.size-1)}#{@stack.join(' < ')})] step >#{name}< - done in #{diff_time} sec(s)" 70 | @stack.pop 71 | else 72 | puts "!! ERROR: step definition >#{name}< not found; cannot run/execute - known (defined) steps include:" 73 | pp self.class.step_methods #=> e.g. [:hello, ...] 74 | exit 1 75 | end 76 | end # method step 77 | 78 | 79 | def self.step_methods 80 | names = instance_methods.reduce([]) do |names, name| 81 | names << $1.to_sym if name =~ /^step_(.+)/ 82 | names 83 | end 84 | names 85 | end 86 | end # class Base 87 | 88 | 89 | end # module Flow -------------------------------------------------------------------------------- /flow-lite/lib/flow-lite/tool.rb: -------------------------------------------------------------------------------- 1 | module Flow 2 | 3 | class Tool 4 | def self.main( args=ARGV ) 5 | options = {} 6 | OptionParser.new do |parser| 7 | parser.on( '-f FILE', '--file FILE', '--flowfile FILE', 8 | 'Read FILE as a flowfile.' 9 | ) do |file| 10 | options[:flowfile] = file 11 | end 12 | 13 | ## note: 14 | ## you can add many/multiple modules 15 | ## e.g. -r gitti -r mono etc. 16 | parser.on( '-r NAME', '--require NAME') do |name| 17 | options[:requires] ||= [] 18 | options[:requires] << name 19 | end 20 | 21 | parser.on( '-d', '--debug') do |debug| 22 | options[:debug] = debug 23 | ## note: for no auto-set env here - why? why not 24 | ENV['DEBUG']='1' ## use t or true or such - why? why not? 25 | puts "[flow] set >DEBUG< env variable to >1<" 26 | end 27 | 28 | ## todo/check/reserve: add -e/--env(iornmanet) e.g. dev/test/prod(uction) etc. 29 | ## plus (convenience) shortcuts e.g. --dev, --prod(uction), --test 30 | end.parse!( args ) 31 | 32 | 33 | ## check for any (dynamic/auto) requires 34 | if options[:requires] 35 | names = options[:requires] 36 | names.each do |name| 37 | ## todo/check: add some error/exception handling here - why? why not? 38 | puts "[flow] auto-require >#{name}<..." 39 | require( name ) 40 | end 41 | else ## use/try defaults 42 | config_path = "./config.rb" 43 | if File.exist?( config_path ) 44 | puts "[flow] auto-require (default) >#{config_path}<..." 45 | require( config_path ) 46 | end 47 | end 48 | 49 | 50 | path = nil 51 | if options[:flowfile] 52 | path = options[:flowfile] 53 | else 54 | path = Flowfile.find_file 55 | 56 | if path.nil? 57 | STDERR.puts "!! ERROR - no flowfile found, sorry - looking for: #{Flowfile::NAMES.join(', ')} in (#{Dir.pwd})" 58 | exit 1 59 | end 60 | end 61 | 62 | 63 | ### split args into vars and steps 64 | vars = [] 65 | args = args.select do |arg| 66 | ## note: mark freestanding = as fatal error (empty var) 67 | if arg == '=' 68 | STDERR.puts "!! ERROR - empty var (=) in args; sorry - make sure there are NO spaces before =" 69 | exit 1 70 | elsif arg =~ /^[A-Za-z][A-Za-z0-9_]*=/ 71 | vars << arg 72 | false ## filter -- do NOT include 73 | else 74 | true 75 | end 76 | end 77 | 78 | if args.size > 0 79 | puts "[flow] #{args.size} arg(s):" 80 | pp args 81 | end 82 | 83 | if vars.size > 0 84 | puts "[flow] #{vars.size} var(s):" 85 | pp vars 86 | 87 | ## auto-add vars to ENV 88 | ## note: if the value is empty e.g. DEBUG= or such 89 | ## the variable gets deleted / undefined / unset!! 90 | vars.each do |var| 91 | pos = var.index('=') ## split on first = 92 | name = var[0..(pos-1)] 93 | value = var[(pos+1)..-1] 94 | # print "[flow] splitting " 95 | # print "%-24s " % ">#{var}<" 96 | # print "=> name: " 97 | # print "%-18s " % ">#{name}<," 98 | # print "value: >#{value}< (#{value.class.name})" 99 | # print "\n" 100 | 101 | if value.empty? ## note: variable gets deleted / undefined / unset 102 | puts "[flow] UNSET >#{name}< env variable" 103 | ENV[ name ] = nil 104 | else 105 | puts "[flow] set >#{name}< env variable to >#{value}<" 106 | ENV[ name ] = value 107 | end 108 | end 109 | end 110 | 111 | 112 | 113 | puts "[flow] loading >#{path}<..." 114 | flowfile = Flowfile.load_file( path ) 115 | 116 | 117 | 118 | ## allow multipe steps getting called - why? why not? 119 | ## flow setup clone update etc. - yes, yes 120 | ## follow make model and allow variables with FOO= or bar= too 121 | ## note: mark freestanding = as fatal error (empty var) 122 | 123 | args.each do |arg| 124 | flowfile.run( arg ) 125 | end 126 | end # method self.main 127 | end # class Tool 128 | 129 | 130 | end # module Flow -------------------------------------------------------------------------------- /flow-lite/lib/flow-lite/version.rb: -------------------------------------------------------------------------------- 1 | module FlowLite 2 | 3 | MAJOR = 1 ## todo: namespace inside version or something - why? why not?? 4 | MINOR = 1 5 | PATCH = 0 6 | VERSION = [MAJOR,MINOR,PATCH].join('.') 7 | 8 | def self.version 9 | VERSION 10 | end 11 | 12 | def self.banner 13 | "flow-lite/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" 14 | end 15 | 16 | def self.root 17 | File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) ) 18 | end 19 | 20 | end # module FlowLite 21 | -------------------------------------------------------------------------------- /flow-lite/make/Makefile: -------------------------------------------------------------------------------- 1 | ## try multiple targets and vars passing in e.g. 2 | ## $ make foo BAR=bbb bar FOO=aaa -or- 3 | ## $ make foo bar BAR=bbb FOO=aaa -or- 4 | ## $ make BAR=bbb FOO=aaa foo bar 5 | ## does the order of vars matter? 6 | ## => NO - all the same 7 | ## results in: 8 | ## aaa 9 | ## bbb 10 | 11 | 12 | .PHONY: foo bar ## PHONY needed? why? why not? 13 | 14 | BAR := xxx_bar 15 | FOO := yyy_foo 16 | 17 | foo: ; @echo $(FOO) $(aaa) 18 | 19 | bar: ; @echo $(BAR) $(aaa) 20 | 21 | -------------------------------------------------------------------------------- /flow-lite/sandbox/Flowfile: -------------------------------------------------------------------------------- 1 | 2 | 3 | step :first_step do 4 | puts "first_step" 5 | 6 | if debug? 7 | puts "debug flag set - hello from debug" 8 | else 9 | puts "no debug flag set" 10 | end 11 | 12 | step :second_step 13 | step_second_step 14 | end 15 | 16 | step :second_step do 17 | puts "second_step" 18 | 19 | step :third_step 20 | step_third_step 21 | end 22 | 23 | step :third_step do 24 | puts "third_step" 25 | end 26 | 27 | 28 | puts "hello from Flowfile" -------------------------------------------------------------------------------- /flow-lite/sandbox/Flowfile2: -------------------------------------------------------------------------------- 1 | DATASETS = %w[a b c] 2 | 3 | 4 | step [:hi,:hello] do 5 | puts "hi" 6 | pp DATASETS 7 | end 8 | 9 | 10 | require 'gitti' 11 | 12 | step :git do 13 | Git.version 14 | end 15 | -------------------------------------------------------------------------------- /flow-lite/sandbox/test_run.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift( "./lib" ) 2 | require 'flow-lite' 3 | 4 | 5 | flowfile = Flow::Flowfile.load_file( './sandbox/Flowfile' ) 6 | pp flowfile 7 | 8 | 9 | flowfile.run( :first_step ) 10 | 11 | 12 | 13 | puts 14 | puts "---" 15 | flowfile = Flow::Flowfile.load( < exlclude all inherited methods 34 | pp flow.class.instance_methods.grep( /^step_/ ) 35 | flow.step_hello 36 | flow.step_hi 37 | flow.step_grüezi 38 | flow.step_hola 39 | 40 | flow.step( :hello ) 41 | flow.step( :hi ) 42 | flow.step( :grüezi ) 43 | flow.step( :hola ) 44 | 45 | 46 | DATASETS = %w[a b c] 47 | 48 | flowfile = Flow::Flowfile.new 49 | pp flowfile 50 | flowfile.step :hi do puts "hi"; pp DATASETS; end 51 | 52 | 53 | require 'gitti' 54 | 55 | flowfile.step :git do 56 | Git.version 57 | end 58 | 59 | pp flowfile 60 | flowfile.run( :hi ) 61 | flowfile.run( :git ) 62 | 63 | # flowfile.run( :bye ) 64 | 65 | puts "bye" 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /flow-lite/sandbox/test_tool.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift( "./lib" ) 2 | require 'flow-lite' 3 | 4 | 5 | Flow::Tool.main -------------------------------------------------------------------------------- /flowfile/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /lib/bundler/man/ 26 | 27 | # for a library or gem, you might want to ignore these files since the code is 28 | # intended to run in multiple environments; otherwise, check them in: 29 | # Gemfile.lock 30 | # .ruby-version 31 | # .ruby-gemset 32 | 33 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 34 | .rvmrc 35 | 36 | 37 | # more debugging support 38 | errors.log -------------------------------------------------------------------------------- /flowfile/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.1 / 2020-10-29 2 | 3 | * Everything is new. First release. 4 | -------------------------------------------------------------------------------- /flowfile/Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | Manifest.txt 3 | README.md 4 | Rakefile 5 | lib/flowfile.rb 6 | -------------------------------------------------------------------------------- /flowfile/README.md: -------------------------------------------------------------------------------- 1 | # flowfile - read in / parse (work)flow definitions with steps, actions, and more 2 | 3 | 4 | * home :: [github.com/rubycoco/git](https://github.com/rubycoco/git) 5 | * bugs :: [github.com/rubycoco/git/issues](https://github.com/rubycoco/git/issues) 6 | * gem :: [rubygems.org/gems/flowfile](https://rubygems.org/gems/flowfile) 7 | * rdoc :: [rubydoc.info/gems/flowfile](http://rubydoc.info/gems/flowfile) 8 | 9 | 10 | 11 | ## Usage 12 | 13 | To be done 14 | 15 | 16 | ## License 17 | 18 | The `flowfile` scripts are dedicated to the public domain. 19 | Use it as you please with no restrictions whatsoever. 20 | 21 | -------------------------------------------------------------------------------- /flowfile/Rakefile: -------------------------------------------------------------------------------- 1 | require 'hoe' 2 | 3 | Hoe.spec 'flowfile' do 4 | 5 | self.version = '0.0.1' # note: for now add version inline 6 | 7 | self.summary = "flowfile - read in / parse (work)flow definitions with steps, actions, and more" 8 | self.description = summary 9 | 10 | self.urls = { home: 'https://github.com/rubycoco/git' } 11 | 12 | self.author = 'Gerald Bauer' 13 | self.email = 'opensport@googlegroups.com' 14 | 15 | # switch extension to .markdown for gihub formatting 16 | self.readme_file = 'README.md' 17 | self.history_file = 'CHANGELOG.md' 18 | 19 | self.licenses = ['Public Domain'] 20 | 21 | self.extra_deps = [] 22 | 23 | self.spec_extras = { 24 | required_ruby_version: '>= 2.2.2' 25 | } 26 | end 27 | -------------------------------------------------------------------------------- /flowfile/lib/flowfile.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # a placeholder for flow-lite for now, stay tuned for more 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gemverse/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /vendor/bundle 26 | /lib/bundler/man/ 27 | 28 | # for a library or gem, you might want to ignore these files since the code is 29 | # intended to run in multiple environments; otherwise, check them in: 30 | # Gemfile.lock 31 | # .ruby-version 32 | # .ruby-gemset 33 | 34 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 35 | .rvmrc 36 | 37 | 38 | #### 39 | ## add cache for gems api requests 40 | cache/ 41 | 42 | -------------------------------------------------------------------------------- /gemverse/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.1 / 2023-01-20 2 | 3 | * Everything is new. First release. 4 | -------------------------------------------------------------------------------- /gemverse/Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | Manifest.txt 3 | README.md 4 | Rakefile 5 | lib/gemverse.rb 6 | lib/gemverse/api.rb 7 | lib/gemverse/cache.rb 8 | lib/gemverse/gems.rb 9 | lib/gemverse/timeline.rb 10 | -------------------------------------------------------------------------------- /gemverse/NOTES.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | ## Gem Gems In Ruby 4 | 5 | 6 | ### Gem Stats & More 7 | 8 | **whatthegem** && **any_good** see 9 | - 10 | - 11 | 12 | 13 | 14 | ### Gem API Wrappers / Calls 15 | 16 | **gems** see 17 | - -------------------------------------------------------------------------------- /gemverse/README.md: -------------------------------------------------------------------------------- 1 | # Gemverse - Gem Universe 2 | 3 | gemverse gem - gem universe incl. rubygems API V1 wrapper lite; gem version cache, gem timeline reports, 'n' more 4 | 5 | 6 | 7 | * home :: [github.com/rubycocos/git](https://github.com/rubycocos/git) 8 | * bugs :: [github.com/rubycocos/git/issues](https://github.com/rubycocos/git/issues) 9 | * gem :: [rubygems.org/gems/gemverse](https://rubygems.org/gems/gemverse) 10 | * rdoc :: [rubydoc.info/gems/gemverse](http://rubydoc.info/gems/gemverse) 11 | 12 | 13 | 14 | 15 | 16 | ## Usage 17 | 18 | 19 | ### RubyGems API "To The Metal" Wrapper V1 - Lite Edition 20 | 21 | The gemverse includes a lightweight "to the metal" 22 | wrapper for the rubygems API V1 23 | that returns data(sets) in the JSON format: 24 | 25 | ``` ruby 26 | require 'gemverse' 27 | 28 | ## get all gems owned by the author with the handle / known as gettalong 29 | # same as https://rubygems.org/api/v1/owners/gettalong/gems.json 30 | data = Gems::API.gems_by( 'gettalong' ) 31 | puts " #{data.size} record(s)" 32 | #=> 24 record(s) 33 | 34 | ## get all versions of the hexapdf gem 35 | # same as https://rubygems.org/api/v1/versions/hexpdf.json" 36 | data = Gems::API.versions( 'hexapdf' ) 37 | puts " #{data.size} record(s)" 38 | #=> 71 record(s) 39 | 40 | #... 41 | ``` 42 | 43 | 44 | ### Gem Cache 'n' Timeline Reports 45 | 46 | Let's build a gem timeline report / what's news page. 47 | Let's spotlight the work of [Thomas Leitner, Austria also known as `gettalong`](https://rubygems.org/profiles/gettalong) 48 | who 24 published gems (as of 2023) in the last 10+ years. 49 | 50 | Note: Yes, you can. Replace the `gettalong` rubygems id / login with your own to build your very own timeline. 51 | 52 | 53 | **Step 1 - Online - Get gems & versions via "higher-level" rubygems api calls** 54 | 55 | ``` ruby 56 | cache = Gems::Cache.new( './cache' ) 57 | 58 | gems = Gems.find_by( owner: 'gettalong' ) 59 | puts " #{gems.size} record(s)" 60 | #=> 24 record(s) 61 | 62 | ## bonus: save gems in a "flat" tabular datafile using the comma-separated values (.csv) format 63 | gems.export( './profiles/gettalong/gems.csv' ) 64 | 65 | ## fetch all gem versions and (auto-save) 66 | ## in a "flat" tabular datafile (e.g. /versions.csv) 67 | ## using the comma-spearated values (.csv) format 68 | ## in the cache directory 69 | cache.update_versions( gems: gems ) 70 | ``` 71 | 72 | 73 | **Step 2 - Offline - Read versions from cache and build reports / timeline** 74 | 75 | ``` ruby 76 | cache = Gems::Cache.new( './gems' ) 77 | 78 | gems = read_csv( './profiles/gettalong/gems.csv' ) 79 | puts " #{gems.size} record(s)" 80 | #=> 24 record(s) 81 | 82 | versions = cache.read_versions( gems: gems ) 83 | puts " #{versions.size} record(s)" 84 | #=> 238 record(s) 85 | 86 | timeline = Gems::Timeline.new( versions, 87 | title: "Thomas Leitner's Timeline" ) 88 | timeline.save( "./profiles/gettalong/README.md" ) 89 | ``` 90 | 91 | 92 | That's it. 93 | 94 | 95 | 96 | Tip: You can build "custom" timeline reports 97 | and filter / select the gems to include as you like. 98 | Let's (re)build the timeline for all ruby cocos (code commons) 99 | gems. 100 | 101 | ``` ruby 102 | cache = Gems::Cache.new( './cache' ) 103 | 104 | gems = read_csv( './collections/cocos.csv' ) 105 | puts " #{gems.size} record(s)" 106 | 107 | versions = cache.read_versions( gems: gems ) 108 | puts " #{versions.size} record(s)" 109 | 110 | timeline = Gems::Timeline.new( versions, 111 | title: 'Ruby Code Commons (COCOS) Timeline' ) 112 | timeline.save( "./collections/cocos/README.md" ) 113 | ``` 114 | 115 | That's it. 116 | 117 | 118 | 119 | See 120 | [Thomas Leitner's Timeline](https://github.com/rubycocos/gems/tree/master/profiles/gettalong), 121 | [Jan Lelis's Timeline](https://github.com/rubycocos/gems/tree/master/profiles/janlelis), 122 | [Ruby Code Commons (COCOS) Timeline](https://github.com/rubycocos/gems/tree/master/collections/cocos), and some more 123 | for some real-world timeline samples. 124 | 125 | Or the [Gerald Bauer's Gem Timeline (By Week) - 244 Gems, 1652 Updates](https://geraldb.github.io/gems/) page. 126 | 127 | Or the gems leaderboard at the [Vienna.rb / Wien.rb - Ruby Meetup / Stammtisch in Vienna, Austria](https://viennarb.github.io/) page. 128 | 129 | 130 | 131 | Yes, you can. [Tell us about your gem timeline / leaderboard sample(s) »](https://github.com/rubycocos/git/issues). 132 | 133 | 134 | 135 | ## License 136 | 137 | The `gemverse` scripts are dedicated to the public domain. 138 | Use it as you please with no restrictions whatsoever. 139 | 140 | -------------------------------------------------------------------------------- /gemverse/Rakefile: -------------------------------------------------------------------------------- 1 | require 'hoe' 2 | 3 | ### 4 | # hack/ quick fix for broken intuit_values - overwrite with dummy 5 | class Hoe 6 | def intuit_values( input ); end 7 | end 8 | 9 | 10 | Hoe.spec 'gemverse' do 11 | 12 | self.version = '0.1.1' # note: for now add version inline 13 | 14 | self.summary = "gemverse gem - gem universe incl. rubygems API V1 wrapper lite; gem version cache, gem timeline reports, 'n' more" 15 | self.description = summary 16 | 17 | self.urls = { home: 'https://github.com/rubycocos/git' } 18 | 19 | self.author = 'Gerald Bauer' 20 | self.email = 'opensport@googlegroups.com' 21 | 22 | # switch extension to .markdown for gihub formatting 23 | self.readme_file = 'README.md' 24 | self.history_file = 'CHANGELOG.md' 25 | 26 | self.licenses = ['Public Domain'] 27 | 28 | self.extra_deps = [ 29 | ['cocos'], 30 | ] 31 | 32 | self.spec_extras = { 33 | required_ruby_version: '>= 2.2.2' 34 | } 35 | end 36 | -------------------------------------------------------------------------------- /gemverse/lib/gemverse.rb: -------------------------------------------------------------------------------- 1 | require 'cocos' 2 | 3 | 4 | ## move to cocos - upstream - why? why not? 5 | def write_csv( path, recs ) 6 | buf = String.new 7 | buf << recs[0].join( ', ' ) 8 | buf << "\n" 9 | recs[1..-1].each do |values| 10 | buf << values.join( ', ' ) 11 | buf << "\n" 12 | end 13 | write_text( path, buf ) 14 | end 15 | 16 | 17 | 18 | 19 | 20 | 21 | require_relative 'gemverse/api' 22 | require_relative 'gemverse/gems' 23 | require_relative 'gemverse/cache' 24 | 25 | require_relative 'gemverse/timeline' ## timeline report 26 | 27 | 28 | 29 | module Gems 30 | 31 | ### 32 | ## "high-level" finders 33 | 34 | def self.find_by( owner: ) ## todo/check: use 35 | 36 | rows = API.gems_by( owner ) 37 | # pp data 38 | puts " #{rows.size} record(s) founds" 39 | 40 | ## write "raw respone" to cache for debugging 41 | write_json( "./cache/gems_#{owner}.json", rows ) 42 | 43 | 44 | 45 | recs = [] 46 | rows.each do |row| 47 | recs << Gem.create( row ) 48 | end 49 | 50 | ## sort by 51 | ## 1) (latest) version created and 52 | ## 2) name 53 | recs = recs.sort do |l,r| 54 | res = r.version_created <=> l.version_created 55 | res = l.name <=> r.name if res == 0 56 | res 57 | end 58 | 59 | GemDataset.new( recs ) 60 | end 61 | 62 | 63 | 64 | def self.read_csv( path ) 65 | ## note: requires Kernel:: otherwise stackoverflow endlessly calling read_csv 66 | rows = Kernel::read_csv( path ) 67 | puts " #{rows.size} record(s) founds" 68 | 69 | recs = [] 70 | rows.each do |row| 71 | kwargs = { 72 | version: row['version'].empty? ? nil : row['version'], 73 | version_downloads: row['version_downloads'].empty? ? nil : row['version_downloads'].to_i, 74 | version_created: row['version_created'].empty? ? nil : Date.strptime( row['version_created'], '%Y-%m-%d' ), 75 | homepage: row['homepage'].empty? ? nil : row['homepage'], 76 | runtime: row['dependencies'].empty? ? [] : row['dependencies'].split('|').map {|dep| dep.strip }, 77 | } 78 | recs << Gem.new( name: row['name'], **kwargs ) 79 | end 80 | 81 | ## sort by 82 | ## 1) (latest) version created and 83 | ## 2) name 84 | recs = recs.sort do |l,r| 85 | res = r.version_created <=> l.version_created 86 | res = l.name <=> r.name if res == 0 87 | res 88 | end 89 | 90 | GemDataset.new( recs ) 91 | end 92 | 93 | 94 | end # module Gems 95 | 96 | -------------------------------------------------------------------------------- /gemverse/lib/gemverse/api.rb: -------------------------------------------------------------------------------- 1 | 2 | module Gems 3 | module API 4 | 5 | BASE = 'https://rubygems.org/api/v1' 6 | 7 | def self.gems_by( owner ) 8 | src = "#{BASE}/owners/#{owner}/gems.json" 9 | call( src ) 10 | end 11 | 12 | 13 | def self.versions( name ) 14 | ## note: will NOT include yanked versions 15 | ## check if there's a query parameter ??? 16 | src = "#{BASE}/versions/#{name}.json" 17 | call( src ) 18 | end 19 | 20 | 21 | def self.call( src ) ## get response as (parsed) json (hash table) 22 | 23 | headers = { 24 | # 'User-Agent' => "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36", 25 | 'User-Agent' => "ruby v#{RUBY_VERSION}", 26 | } 27 | 28 | response = Webclient.get( src, headers: headers ) 29 | 30 | if response.status.ok? 31 | puts "#{response.status.code} #{response.status.message} - content_type: #{response.content_type}, content_length: #{response.content_length}" 32 | 33 | response.json 34 | else 35 | puts "!! HTTP ERROR:" 36 | puts "#{response.status.code} #{response.status.message}" 37 | exit 1 38 | end 39 | end 40 | 41 | end # module API 42 | end # module Gems 43 | -------------------------------------------------------------------------------- /gemverse/lib/gemverse/cache.rb: -------------------------------------------------------------------------------- 1 | 2 | 3 | module Gems 4 | class Cache 5 | def initialize( basedir='./gems' ) 6 | @basedir = basedir 7 | end 8 | 9 | 10 | def update_versions( gems: [] ) 11 | 12 | delay_in_s = 0.5 13 | 14 | gems.each_with_index do |gem,i| 15 | 16 | name = if gem.is_a?( String ) 17 | gem 18 | elsif gem.is_a?( Hash ) 19 | gem['name'] 20 | else ## assume our own Gem struct/class for now 21 | gem.name 22 | end 23 | 24 | puts "==> #{i+1}/#{gems.size} #{name}..." 25 | 26 | ## update versions info 27 | puts " sleeping #{delay_in_s} second(s)" 28 | sleep( delay_in_s ) 29 | 30 | data = Gems::API.versions( name ) 31 | puts " #{data.size} version record(s) found" 32 | 33 | headers = ['name', 34 | 'version', 35 | 'created', 36 | 'downloads'] 37 | recs = [] 38 | data.each do |h| 39 | ## only use year/month/day for now now hours etc. 40 | created = Date.strptime( h['created_at'], '%Y-%m-%d' ) 41 | 42 | rec = [ 43 | name, 44 | h['number'], 45 | created.strftime( '%Y-%m-%d' ), 46 | h['downloads_count'].to_s, 47 | ] 48 | recs << rec 49 | end 50 | write_csv( "#{@basedir}/#{name}/versions.csv", [headers]+recs ) 51 | end 52 | end 53 | 54 | 55 | 56 | def read_versions( gems: [] ) 57 | ## read in and merge all version records 58 | versions = [] 59 | 60 | ## if no gems passed in - get all versions.csv datasets in basedir 61 | if gems.empty? 62 | paths = Dir.glob( "#{@basedir}/*/versions.csv" ) 63 | gems = paths.map { |path| File.basename(File.dirname(path)) } 64 | end 65 | 66 | gems.each_with_index do |gem,i| 67 | 68 | name = if gem.is_a?( String ) 69 | gem 70 | elsif gem.is_a?( Hash ) 71 | gem['name'] 72 | else ## assume our own Gem struct/class for now 73 | gem.name 74 | end 75 | 76 | path = "#{@basedir}/#{name}/versions.csv" 77 | puts "==> #{i+1}/#{gems.size} reading #{name}..." 78 | recs = read_csv( path ) 79 | recs.reverse.each_with_index do |rec,n| 80 | more = { 'count' => (n+1).to_s } ## auto-add version count(er) e.g. 1,2,3,... 81 | versions << rec.merge( more ) 82 | end 83 | puts " #{recs.size} record(s)" 84 | end 85 | 86 | ## sort by version created and name 87 | versions = versions.sort do |l,r| 88 | res = r['created'] <=> l['created'] 89 | res = l['name'] <=> r['name'] if res == 0 90 | res = r['version'] <=> l['version'] if res == 0 91 | res 92 | end 93 | 94 | versions 95 | end 96 | 97 | 98 | 99 | end ## class Cache 100 | end ## module Gems 101 | -------------------------------------------------------------------------------- /gemverse/lib/gemverse/gems.rb: -------------------------------------------------------------------------------- 1 | 2 | module Gems 3 | 4 | 5 | 6 | class GemDataset ## rename to Gems or Gemset or such - why? why not? 7 | 8 | 9 | def initialize( gems ) 10 | @gems = gems 11 | end 12 | 13 | def size() @gems.size; end 14 | def each_with_index( &block ) @gems.each_with_index( &block ); end 15 | def each( &block ) @gems.each( &block ); end 16 | 17 | 18 | ## todo/check: add an write_csv( path ) alias / alternate method name - why? why not? 19 | def export( path ) 20 | recs = [] 21 | 22 | @gems.each do |gem| 23 | 24 | if gem.yanked? 25 | puts "!! ERROR - includes yanked gem" 26 | pp gem 27 | exit 1 28 | end 29 | 30 | rec = [gem.name, 31 | gem.version, 32 | gem.version_created.strftime( '%Y-%m-%d' ), 33 | gem.version_downloads.to_s, 34 | gem.homepage, 35 | gem.runtime_dependencies.join( ' | ' ), 36 | ] 37 | recs << rec 38 | end 39 | headers = ['name', 40 | 'version', 41 | 'version_created', 42 | 'version_downloads', 43 | 'homepage', 44 | 'dependencies', 45 | ] 46 | write_csv( path, [headers]+recs ) 47 | end # method export 48 | end ## class GemDataset 49 | 50 | 51 | 52 | 53 | class Gem ## todo/check: rename or use Gem::Meta or such - why? why not? 54 | 55 | attr_reader :name, 56 | :version, :version_created, :version_downloads, 57 | :homepage, 58 | :runtime 59 | 60 | 61 | def self.create( h ) ## todo/check: rename to create_from_json or such - why? why not? 62 | ## optional keyword args 63 | kwargs = { 64 | version: h['version'], 65 | ## only use year/month/day for now now hours etc. - why? why not 66 | version_created: h['version_created_at'] ? Date.strptime( h['version_created_at'], '%Y-%m-%d' ) : nil, 67 | version_downloads: h['version_downloads'], 68 | ## note: (auto-)clean 69 | ## for example - newline seen in "http://icanhasaudio.com/\n" !!! 70 | homepage: h['homepage_uri'] ? h['homepage_uri'].gsub( /[ \r\n]/, '') 71 | : nil, 72 | yanked: h['yanked'], 73 | runtime: h['dependencies']['runtime'].map { |dep| dep['name'] } 74 | } 75 | 76 | new( name: h['name'], 77 | **kwargs ) 78 | end 79 | 80 | 81 | def initialize( name:, 82 | version: nil, 83 | version_created: nil, 84 | version_downloads: nil, 85 | homepage: nil, 86 | yanked: nil, 87 | runtime: [] ) 88 | 89 | @name = name 90 | @version = version 91 | @version_created = version_created 92 | @version_downloads = version_downloads 93 | @homepage = homepage 94 | @yanked = yanked 95 | @runtime = runtime 96 | end 97 | 98 | alias_method :runtime_dependencies, :runtime 99 | 100 | ## always return false if not defined - why? why not? 101 | def yanked?() @yanked.nil? ? false : @yanked; end 102 | end # class Gems 103 | 104 | 105 | end ## module Gems 106 | -------------------------------------------------------------------------------- /gemverse/sandbox/generate_cocos.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib sandbox/generate_cocos.rb 4 | 5 | 6 | require 'gemverse' 7 | 8 | 9 | 10 | cache = Gems::Cache.new( '../../gems/cache' ) 11 | 12 | 13 | gems = read_csv( '../../gems/collections/cocos/gems.csv' ) 14 | puts " #{gems.size} record(s)" 15 | 16 | 17 | versions = cache.read_versions( gems: gems ) 18 | 19 | pp versions[0,100] 20 | puts " #{versions.size} record(s)" 21 | 22 | 23 | 24 | 25 | timeline = Gems::Timeline.new( versions ) 26 | 27 | ## timeline.export( "../../gems/collections/cocos/versions.csv" ) 28 | 29 | 30 | timeline.save( "../../rubycocos.github.io/README.md", 31 | title: "Ruby Code Commons (COCOS) Gem Timeline (By Month) - #{gems.size} Gems, #{versions.size} Updates" 32 | ) 33 | 34 | 35 | puts "bye" 36 | 37 | -------------------------------------------------------------------------------- /gemverse/sandbox/generate_vienna.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib sandbox/gernerate_vienna.rb 4 | 5 | require 'gemverse' 6 | 7 | 8 | cache = Gems::Cache.new( '../../gems/cache' ) 9 | 10 | 11 | owners = [ 12 | ['geraldbauer', 'Gerald Bauer (Austria, near Vienna)'], 13 | ['gettalong', 'Thomas Leitner (Austria, Vienna)'], 14 | ['informatom', 'Stefan Haslinger (Austria, Vienna)'], 15 | ['noniq', 'Stefan Daschek (Austria, Vienna)'], 16 | ['pferdefleisch', 'Aaron Cruz (Austria, near Vienna)'], 17 | ['ramonh', 'Ramón Huidobro (Austria, Vienna)'], 18 | ] 19 | 20 | 21 | summary = [] 22 | 23 | ## step 2: build reports 24 | owners.each do |owner, name| 25 | gems = read_csv( "../../gems/profiles/#{owner}/gems.csv" ) 26 | 27 | ## step 2: read all versions (from cache) 28 | versions = cache.read_versions( gems: gems ) 29 | 30 | pp versions[0,100] 31 | puts " #{versions.size} record(s)" 32 | 33 | timeline = Gems::Timeline.new( versions ) 34 | # timeline.export( "../../viennarb.github.io/profiles/#{owner}/versions.csv" ) 35 | 36 | 37 | timeline.save( "../../viennarb.github.io/profiles/#{owner}/README.md", 38 | title: "#{name}'s Gem Timeline (By Month) - #{gems.size} Gems, #{versions.size} Updates" ) 39 | 40 | summary << "- #{gems.size} Gems, #{versions.size} Updates - [#{name}'s Gem Timeline (By Month)](profiles/#{owner})" 41 | end 42 | 43 | 44 | puts "summary:" 45 | puts summary.join("\n") 46 | 47 | 48 | puts "bye" 49 | 50 | 51 | -------------------------------------------------------------------------------- /gemverse/sandbox/generate_yo.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib sandbox/generate_yo.rb 4 | 5 | require 'gemverse' 6 | 7 | 8 | 9 | cache = Gems::Cache.new( '../../gems/cache' ) 10 | 11 | gems = read_csv( '../../gems/profiles/geraldbauer/gems.csv' ) 12 | puts " #{gems.size} record(s)" 13 | 14 | 15 | versions = cache.read_versions( gems: gems ) 16 | # versions = cache.read_versions( gems: ['slideshow'] ) 17 | 18 | pp versions[0,100] 19 | puts " #{versions.size} record(s)" 20 | 21 | 22 | 23 | 24 | timeline = Gems::Timeline.new( versions ) 25 | 26 | # timeline.export( "../../gems/profiles/geraldbauer/versions.csv" ) 27 | 28 | buf = timeline.build_by_week( title: "Gerald Bauer's Gem Timeline (By Week) - #{gems.size} Gems, #{versions.size} Updates" 29 | ) 30 | puts buf 31 | 32 | 33 | write_text( "./tmp/timeline.md", buf ) 34 | write_text( "../../geraldb.github.io/gems/README.md", buf ) 35 | 36 | 37 | puts "bye" 38 | 39 | -------------------------------------------------------------------------------- /gemverse/sandbox/test_api.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib sandbox/test_api.rb 4 | 5 | require 'gemverse' 6 | 7 | data = Gems::API.gems_by( 'gettalong' ) 8 | puts " #{data.size} record(s)" 9 | #=> 24 record(s) 10 | 11 | data = Gems::API.versions( 'hexapdf' ) 12 | puts " #{data.size} record(s)" 13 | #=> 71 record(s) 14 | 15 | 16 | 17 | 18 | data = Gems::API.gems_by( 'geraldbauer' ) 19 | 20 | pp data 21 | puts " #{data.size} record(s)" 22 | 23 | 24 | # name = 'ethlite' 25 | name = 'slideshow' 26 | data = Gems::API.versions( name ) 27 | pp data 28 | 29 | recs = [] 30 | data.each do |h| 31 | 32 | ## only use year/month/day for now now hours etc. 33 | created = Date.strptime( h['created_at'], '%Y-%m-%d' ) 34 | 35 | rec = [ 36 | name, 37 | h['number'], 38 | created.strftime( '%Y-%m-%d' ), 39 | h['downloads_count'].to_s, 40 | ] 41 | recs << rec 42 | end 43 | 44 | pp recs 45 | 46 | puts "bye" 47 | -------------------------------------------------------------------------------- /gemverse/sandbox/timeline_step1.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib sandbox/timeline_step1.rb 4 | 5 | require 'gemverse' 6 | 7 | 8 | cache = Gems::Cache.new( '../../gems/cache' ) 9 | 10 | 11 | owners = [ 12 | # 'janlelis', ## 100 gems by Jan Lelis, Germany 13 | # 'ankane', 14 | # 'zverok', 15 | # 'q9', 16 | ] 17 | 18 | 19 | at_owners = [ 20 | # 'informatom', ## 11 gems by Stefan Haslinger 21 | # 'noniq', ## 8 gems by Stefan Daschek 22 | # 'pferdefleisch', ## 10 gems by Aaron Cruz 23 | # 'ramonh', ## 1 gem by Ramón Huidobro 24 | # 'gettalong', ## 24 gems by Thomas Leitner, Austria 25 | ] 26 | 27 | 28 | 29 | owners = at_owners 30 | 31 | ## step 1: get gems & versions data via rubygems api 32 | owners.each do |owner| 33 | gems = Gems.find_by( owner: owner ) 34 | puts " #{gems.size} record(s)" 35 | 36 | gems.export( "../../gems/profiles/#{owner}/gems.csv" ) 37 | 38 | cache.update_versions( gems: gems ) 39 | end 40 | 41 | 42 | puts "bye" 43 | 44 | 45 | -------------------------------------------------------------------------------- /gemverse/sandbox/timeline_step2.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib sandbox/timeline_step2.rb 4 | 5 | require 'gemverse' 6 | 7 | 8 | cache = Gems::Cache.new( '../../gems/cache' ) 9 | 10 | 11 | owners = [ 12 | ['geraldbauer', 'Gerald Bauer (Austria, near Vienna)'], 13 | ['gettalong', 'Thomas Leitner (Austria, Vienna)'], 14 | ['janlelis', 'Jan Lelis (Germany, Berlin)'], 15 | ['ankane', 'Andrew Kane (San Francisco, United States)'], 16 | ['zverok', 'Victor Shepelev (Ukraine, Kharkiv)'], 17 | ['q9', 'Afri Schoedon (Germany, Berlin)'], 18 | ] 19 | 20 | 21 | summary = [] 22 | 23 | ## step 2: build reports 24 | owners.each do |owner, name| 25 | gems = read_csv( "../../gems/profiles/#{owner}/gems.csv" ) 26 | 27 | ## step 2: read all versions (from cache) 28 | versions = cache.read_versions( gems: gems ) 29 | 30 | pp versions[0,100] 31 | puts " #{versions.size} record(s)" 32 | 33 | timeline = Gems::Timeline.new( versions ) 34 | ## timeline.export( "../../gems/profiles/#{owner}/versions.csv" ) 35 | 36 | 37 | timeline.save( "../../gems/profiles/#{owner}/README.md", 38 | title: "#{name}'s Gem Timeline (By Month) - #{gems.size} Gems, #{versions.size} Updates" ) 39 | 40 | summary << "[#{gems.size} Gems, #{versions.size} Updates - #{name}'s Gem Timeline (By Month)](profiles/#{owner})" 41 | end 42 | 43 | 44 | puts "summary:" 45 | puts summary.join("\n") 46 | 47 | 48 | puts "bye" 49 | 50 | 51 | -------------------------------------------------------------------------------- /gemverse/sandbox/update_gems.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib sandbox/update_gems.rb 4 | 5 | require 'gemverse' 6 | 7 | 8 | delay_in_s = 1.0 9 | 10 | owners = [ 11 | # 'geraldbauer' 12 | 'gettalong', ## 24 gems by Thomas Leitner, Austria 13 | # 'zverok', ## 32 gems by Victor Shepelev, Ukraine 14 | # 'tenderlove', ## 177 gems by Aaron Patterson, United States 15 | # 'webster132', ## 79 gems by David Heinemeier Hansson (DHH) 16 | # 'zenspider', ## 98 gems by Ryan Davis, United States 17 | 'janlelis', ## 100 gems by Jan Lelis, Germany 18 | ] 19 | owners.each do |owner| 20 | sleep( delay_in_s ) 21 | gems = Gems.find_by( owner: owner ) 22 | puts " #{gems.size} record(s)" 23 | 24 | gems.export( "../../gems/profiles/#{owner}/gems.csv" ) 25 | end 26 | 27 | 28 | puts "bye" 29 | -------------------------------------------------------------------------------- /gemverse/sandbox/versions.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib sandbox/versions.rb 4 | 5 | require 'gemverse' 6 | 7 | 8 | 9 | 10 | cache = Gems::Cache.new( '../../gems/cache' ) 11 | 12 | gems = read_csv( '../../gems/profiles/gettalong/gems.csv' ) 13 | puts " #{gems.size} record(s)" 14 | 15 | versions = cache.read_versions( gems: gems ) 16 | puts " #{versions.size} record(s)" 17 | 18 | 19 | 20 | ## cache.update_versions( gems: ['slideshow', 'markdown'] ) 21 | ## cache.update_versions( gems: gems[0,3] ) 22 | 23 | 24 | 25 | # cache.update_versions( gems: gems ) 26 | 27 | 28 | puts "bye" 29 | -------------------------------------------------------------------------------- /gitfile/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /lib/bundler/man/ 26 | 27 | # for a library or gem, you might want to ignore these files since the code is 28 | # intended to run in multiple environments; otherwise, check them in: 29 | # Gemfile.lock 30 | # .ruby-version 31 | # .ruby-gemset 32 | 33 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 34 | .rvmrc 35 | 36 | 37 | # more debugging support 38 | errors.log -------------------------------------------------------------------------------- /gitfile/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.1 / 2020-10-29 2 | 3 | * Everything is new. First release. 4 | -------------------------------------------------------------------------------- /gitfile/Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | Manifest.txt 3 | README.md 4 | Rakefile 5 | lib/gitfile.rb 6 | -------------------------------------------------------------------------------- /gitfile/NOTES.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | 4 | ## More Gitfiles tools 5 | 6 | ### Gitfile (Go) 7 | 8 | _a (light-weight) package manager for installing and updating projects from git repos_ 9 | 10 | - by Brad Urani 11 | - 12 | - 13 | 14 | ``` yaml 15 | - url: git://github.com/bradurani/bradrc.git 16 | - url: https://github.com/thoughtbot/dotfiles.git 17 | path: thoughtbot/ 18 | - url: https://github.com/olivierverdier/zsh-git-prompt.git 19 | tag: v0.4 20 | - url: https://github.com/tmux-plugins/tmux-battery.git 21 | path: ~/.tmux-plugins/ 22 | branch: master 23 | ``` 24 | 25 | 26 | ### Gitfile (Shell) 27 | 28 | _download and manage multiple git repos_ 29 | 30 | - 31 | - 32 | 33 | Example: 34 | 35 | ``` yaml 36 | gitfile: 37 | source: "https://github.com/Bobonium/gitfile.git" 38 | path: ~/workspace/github/Bobonium/ 39 | version: master 40 | ``` 41 | 42 | 43 | ## More Resources 44 | 45 | - - gitfile topic on github 46 | 47 | 48 | -------------------------------------------------------------------------------- /gitfile/README.md: -------------------------------------------------------------------------------- 1 | # gitfile - a (light-weight) package manager for installing and updating projects from git repos 2 | 3 | * home :: [github.com/rubycoco/git](https://github.com/rubycoco/git) 4 | * bugs :: [github.com/rubycoco/git/issues](https://github.com/rubycoco/git/issues) 5 | * gem :: [rubygems.org/gems/gitfile](https://rubygems.org/gems/gitfile) 6 | * rdoc :: [rubydoc.info/gems/gitfile](http://rubydoc.info/gems/gitfile) 7 | 8 | 9 | 10 | ## Usage 11 | 12 | To be done 13 | 14 | 15 | ## License 16 | 17 | The `gitfile` scripts are dedicated to the public domain. 18 | Use it as you please with no restrictions whatsoever. 19 | 20 | -------------------------------------------------------------------------------- /gitfile/Rakefile: -------------------------------------------------------------------------------- 1 | require 'hoe' 2 | 3 | Hoe.spec 'gitfile' do 4 | 5 | self.version = '0.0.1' # note: for now add version inline 6 | 7 | self.summary = "gitfile - a (light-weight) package manager for installing and updating projects from git repos" 8 | self.description = summary 9 | 10 | self.urls = { home: 'https://github.com/rubycoco/git' } 11 | 12 | self.author = 'Gerald Bauer' 13 | self.email = 'opensport@googlegroups.com' 14 | 15 | # switch extension to .markdown for gihub formatting 16 | self.readme_file = 'README.md' 17 | self.history_file = 'CHANGELOG.md' 18 | 19 | self.licenses = ['Public Domain'] 20 | 21 | self.extra_deps = [] 22 | 23 | self.spec_extras = { 24 | required_ruby_version: '>= 2.2.2' 25 | } 26 | end 27 | -------------------------------------------------------------------------------- /gitfile/lib/gitfile.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # a placeholder for gitfile for now, stay tuned for more 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gitti-backup/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /vendor/bundle 26 | /lib/bundler/man/ 27 | 28 | # for a library or gem, you might want to ignore these files since the code is 29 | # intended to run in multiple environments; otherwise, check them in: 30 | # Gemfile.lock 31 | # .ruby-version 32 | # .ruby-gemset 33 | 34 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 35 | .rvmrc 36 | 37 | #### ignore errors.log 38 | errors.log 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /gitti-backup/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.1 / 2015-11-20 2 | 3 | * Everything is new. First release. 4 | 5 | -------------------------------------------------------------------------------- /gitti-backup/Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | Manifest.txt 3 | README.md 4 | Rakefile 5 | bin/backup 6 | lib/gitti/backup.rb 7 | lib/gitti/backup/backup.rb 8 | lib/gitti/backup/base.rb 9 | lib/gitti/backup/version.rb 10 | test/data/repos.yml 11 | test/helper.rb 12 | test/test_backup.rb 13 | -------------------------------------------------------------------------------- /gitti-backup/NOTES.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | ## Resources 4 | 5 | more git backup projects 6 | 7 | - 8 | - 9 | 10 | 11 | -------------------------------------------------------------------------------- /gitti-backup/README.md: -------------------------------------------------------------------------------- 1 | # gitti-backup 2 | 3 | gitti-backup gem - (yet) another (lite) git backup command line script 4 | 5 | * home :: [github.com/rubycoco/gitti](https://github.com/rubycoco/gitti) 6 | * bugs :: [github.com/rubycoco/gitti/issues](https://github.com/rubycoco/gitti/issues) 7 | * gem :: [rubygems.org/gems/gitti-backup](https://rubygems.org/gems/gitti-backup) 8 | * rdoc :: [rubydoc.info/gems/gitti-backup](http://rubydoc.info/gems/gitti-backup) 9 | 10 | 11 | 12 | ## Usage 13 | 14 | ### `backup` Command Line Tool 15 | 16 | Use the `backup` command line tool to backup all repos listed in a "manifest" file (e.g. `repos.yml`, `code.yml`, `github.yml` or such). Example: 17 | 18 | ``` 19 | backup repos.yml # or 20 | backup code.yml data.yml 21 | ``` 22 | 23 | In a nutshell backup will backup all repos by using 24 | 25 | 1. `git clone --mirror` or 26 | 2. `git remote update` (if the local backup already exists) 27 | 28 | and store all bare repos (without workspace) in the `~/backup` directory. 29 | 30 | 31 | #### Repos Manifest 32 | 33 | For now only github repos are supported listed by 34 | owner / organization. Example `repos.yml`: 35 | 36 | ``` yaml 37 | sportdb: 38 | - sport.db 39 | - sport.db.sources 40 | - football.db 41 | 42 | yorobot: 43 | - cache.csv 44 | - sport.db.more 45 | - football.db 46 | - football.csv 47 | 48 | openfootball: 49 | - leagues 50 | - clubs 51 | ``` 52 | 53 | Using `backup` with defaults this will result in: 54 | 55 | ``` 56 | ~/backup 57 | /sportdb 58 | /sport.db.git 59 | /sport.db.sources.git 60 | /football.db.git 61 | /yorobot 62 | /cache.csv.git 63 | /sport.db.more.git 64 | /football.db.git 65 | /football.csv.git 66 | /openfootball 67 | /leagues.git 68 | /clubs.git 69 | ``` 70 | 71 | 72 | 73 | ### Scripting 74 | 75 | You can script your backup in ruby. Example: 76 | 77 | 78 | ``` ruby 79 | require 'gitti/backup' 80 | 81 | ## step 1: setup the root backup directory - defaults to ~/backup 82 | backup = GitBackup.new 83 | 84 | ## step 2: pass in all repos to backup by using 85 | ## 1) git clone --mirror or 86 | ## 2) git remote update (if local backup already exists) 87 | backup.backup( GitRepoSet.read( './repos.yml' ) ) 88 | ``` 89 | 90 | or 91 | 92 | ``` ruby 93 | require 'gitti/backup' 94 | 95 | ## step 1: setup the root backup directory 96 | ## 1) use custom directory e.g. /backups 97 | ## 2) auto-add a daily directory e.g. /backups/2020-09-27 98 | backup = GitBackup.new( '/backups', daily: true ) 99 | 100 | backup.backup( GitRepoSet.read( './repos.yml' ) ) 101 | ``` 102 | 103 | 104 | 105 | That's all for now. 106 | 107 | 108 | 109 | ## Installation 110 | 111 | Use 112 | 113 | gem install gitti-backup 114 | 115 | or add to your Gemfile 116 | 117 | gem 'gitti-backup' 118 | 119 | 120 | ## License 121 | 122 | The `gitti-backup` scripts are dedicated to the public domain. 123 | Use it as you please with no restrictions whatsoever. 124 | 125 | -------------------------------------------------------------------------------- /gitti-backup/Rakefile: -------------------------------------------------------------------------------- 1 | require 'hoe' 2 | require './lib/gitti/backup/version.rb' 3 | 4 | Hoe.spec 'gitti-backup' do 5 | 6 | self.version = GittiBackup::VERSION 7 | 8 | self.summary = 'gitti-backup - (yet) another (lite) git backup command line script' 9 | self.description = summary 10 | 11 | self.urls = { home: 'https://github.com/rubycoco/gitti' } 12 | 13 | self.author = 'Gerald Bauer' 14 | self.email = 'ruby-talk@ruby-lang.org' 15 | 16 | # switch extension to .markdown for gihub formatting 17 | self.readme_file = 'README.md' 18 | self.history_file = 'CHANGELOG.md' 19 | 20 | self.extra_deps = [ 21 | ['gitti', '>= 0.3.0' ], 22 | ] 23 | 24 | self.licenses = ['Public Domain'] 25 | 26 | self.spec_extras = { 27 | required_ruby_version: '>= 2.2.2' 28 | } 29 | 30 | end 31 | -------------------------------------------------------------------------------- /gitti-backup/bin/backup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ################### 4 | # DEV TIPS: 5 | # 6 | # For local testing run like: 7 | # 8 | # ruby -Ilib bin/backup 9 | # 10 | # Set the executable bit in Linux. Example: 11 | # 12 | # % chmod a+x bin/backup 13 | # 14 | 15 | require 'gitti/backup/base' 16 | 17 | Gitti::GitBackup::Tool.main 18 | -------------------------------------------------------------------------------- /gitti-backup/lib/gitti/backup.rb: -------------------------------------------------------------------------------- 1 | require_relative 'backup/base' 2 | 3 | 4 | ## note: auto include Gitti; for "modular" version use ("Sinatra-style") 5 | ## require "gitti/backup/base" 6 | 7 | include Gitti -------------------------------------------------------------------------------- /gitti-backup/lib/gitti/backup/backup.rb: -------------------------------------------------------------------------------- 1 | 2 | module Gitti 3 | 4 | 5 | class GitBackup 6 | 7 | class Tool ## nested class 8 | def self.main( args=ARGV ) 9 | backup = GitBackup.new 10 | args.each do |arg| 11 | backup.backup( GitRepoSet.read( arg )) 12 | end 13 | end 14 | end ## nested class Tool 15 | 16 | 17 | 18 | def initialize( root= '~/backup', daily: false ) 19 | @root = File.expand_path( root ) 20 | pp @root 21 | 22 | ## use current working dir for the log path; see do_with_log helper for use 23 | @log_path = File.expand_path( '.' ) 24 | pp @log_path 25 | 26 | @daily = daily 27 | end 28 | 29 | 30 | ## auto-add "daily" date folder / dir 31 | ## e.g. 2015-11-20 using Date.today.strftime('%Y-%m-%d') 32 | def daily=(value) @daily=value; end 33 | 34 | 35 | 36 | def backup( repos ) 37 | count_orgs = 0 38 | count_repos = 0 39 | 40 | total_repos = repos.size 41 | 42 | ## default to adding folder per day ## e.g. 2015-11-20 43 | backup_path = "#{@root}" 44 | backup_path << "/#{Date.today.strftime('%Y-%m-%d')}" if @daily 45 | pp backup_path 46 | 47 | FileUtils.mkdir_p( backup_path ) ## make sure path exists 48 | 49 | repos.each do |org, names| 50 | org_path = "#{backup_path}/#{org}" 51 | FileUtils.mkdir_p( org_path ) ## make sure path exists 52 | 53 | names.each do |name| 54 | puts "[#{count_repos+1}/#{total_repos}] #{org}@#{name}..." 55 | 56 | repo = GitHubRepo.new( org, name ) ## owner, name e.g. rubylibs/webservice 57 | 58 | success = Dir.chdir( org_path ) do 59 | if Dir.exist?( "#{repo.name}.git" ) 60 | GitMirror.open( "#{repo.name}.git" ) do |mirror| 61 | do_with_retries { mirror.update } ## note: defaults to two tries 62 | end 63 | else 64 | do_with_retries { Git.mirror( repo.https_clone_url ) } 65 | end 66 | end 67 | 68 | ## todo/check: fail if success still false after x retries? -- why? why not? 69 | 70 | count_repos += 1 71 | end 72 | count_orgs += 1 73 | end 74 | 75 | ## print stats 76 | puts " #{count_repos} repo(s) @ #{count_orgs} org(s)" 77 | end ## backup 78 | 79 | 80 | ###### 81 | # private helpers 82 | 83 | def do_with_log( &blk ) 84 | blk.call 85 | true ## return true ## success/ok 86 | rescue GitError => ex 87 | puts "!! ERROR: #{ex.message}" 88 | 89 | File.open( "#{@log_path}/errors.log", 'a' ) do |f| 90 | f.write "#{Time.now} -- #{ex.message}\n" 91 | end 92 | false ## return false ## error/nok 93 | end 94 | 95 | 96 | def do_with_retries( retries: 2, sleep: 5, &blk ) 97 | retries_count = 0 98 | success = false 99 | loop do 100 | success = do_with_log( &blk ) 101 | retries_count += 1 102 | break if success || retries_count >= retries 103 | puts "retrying in #{sleep}secs... sleeping..." 104 | sleep( sleep ) ## sleep 5secs or such 105 | end 106 | success ## return if success or not (true/false) 107 | end 108 | 109 | end ## class GitBackup 110 | end ## module Gitti 111 | -------------------------------------------------------------------------------- /gitti-backup/lib/gitti/backup/base.rb: -------------------------------------------------------------------------------- 1 | require 'gitti/base' # note: include "modular" base without (auto)include Gitti 2 | 3 | 4 | # our own code 5 | require 'gitti/backup/version' # note: let version always go first 6 | require 'gitti/backup/backup' 7 | 8 | 9 | # say hello 10 | puts GittiBackup.banner ## if defined?($RUBYCOCO_DEBUG) 11 | 12 | -------------------------------------------------------------------------------- /gitti-backup/lib/gitti/backup/version.rb: -------------------------------------------------------------------------------- 1 | 2 | module GittiBackup 3 | 4 | MAJOR = 0 ## todo: namespace inside version or something - why? why not?? 5 | MINOR = 4 6 | PATCH = 1 7 | VERSION = [MAJOR,MINOR,PATCH].join('.') 8 | 9 | def self.version 10 | VERSION 11 | end 12 | 13 | def self.banner 14 | "gitti-backup/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" 15 | end 16 | 17 | def self.root 18 | File.expand_path( File.dirname(File.dirname(File.dirname(File.dirname(__FILE__)))) ) 19 | end 20 | 21 | end # module GittiBackup 22 | 23 | -------------------------------------------------------------------------------- /gitti-backup/test/data/repos.yml: -------------------------------------------------------------------------------- 1 | ########################## 2 | # some repos 3 | 4 | openbeer (2): 5 | - at-austria 6 | - be-belgium 7 | 8 | openfootball (3): 9 | - austria 10 | - mexico 11 | - world-cup 12 | 13 | openmundi (2): 14 | - austria.db 15 | - world.db 16 | 17 | -------------------------------------------------------------------------------- /gitti-backup/test/helper.rb: -------------------------------------------------------------------------------- 1 | ## note: use the local version of gitti gems 2 | $LOAD_PATH.unshift( File.expand_path( '../gitti/lib' )) 3 | 4 | ## minitest setup 5 | require 'minitest/autorun' 6 | 7 | 8 | ## our own code 9 | require 'gitti/backup' 10 | 11 | -------------------------------------------------------------------------------- /gitti-backup/test/test_backup.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib -I ./test test/test_backup.rb 4 | 5 | 6 | require 'helper' 7 | 8 | 9 | class TestBackup < MiniTest::Test 10 | 11 | def test_backup_i 12 | repos = GitRepoSet.read( "#{GittiBackup.root}/test/data/repos.yml" ) 13 | pp repos 14 | 15 | backup = GitBackup.new 16 | assert true ## assume everything ok if get here 17 | end 18 | 19 | def test_backup_ii 20 | repos = GitRepoSet.read( "#{GittiBackup.root}/test/data/repos.yml" ) 21 | pp repos 22 | 23 | backup = GitBackup.new 24 | backup.backup( repos ) 25 | 26 | assert true ## assume everything ok if get here 27 | end 28 | 29 | end # class TestBackup 30 | 31 | -------------------------------------------------------------------------------- /gitti/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /vendor/bundle 26 | /lib/bundler/man/ 27 | 28 | # for a library or gem, you might want to ignore these files since the code is 29 | # intended to run in multiple environments; otherwise, check them in: 30 | # Gemfile.lock 31 | # .ruby-version 32 | # .ruby-gemset 33 | 34 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 35 | .rvmrc 36 | 37 | 38 | -------------------------------------------------------------------------------- /gitti/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.1 / 2015-11-16 2 | 3 | * Everything is new. First release. 4 | 5 | -------------------------------------------------------------------------------- /gitti/Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | Manifest.txt 3 | README.md 4 | Rakefile 5 | lib/gitti.rb 6 | lib/gitti/base.rb 7 | lib/gitti/git.rb 8 | lib/gitti/mirror.rb 9 | lib/gitti/project.rb 10 | lib/gitti/reposet.rb 11 | lib/gitti/version.rb 12 | test/helper.rb 13 | test/test_base.rb 14 | -------------------------------------------------------------------------------- /gitti/README.md: -------------------------------------------------------------------------------- 1 | # gitti 2 | 3 | gitti gem - (yet) another (lite) git command line helper / wrapper 4 | 5 | * home :: [github.com/rubycoco/gitti](https://github.com/rubycoco/gitti) 6 | * bugs :: [github.com/rubycoco/gitti/issues](https://github.com/rubycoco/gitti/issues) 7 | * gem :: [rubygems.org/gems/gitti](https://rubygems.org/gems/gitti) 8 | * rdoc :: [rubydoc.info/gems/gitti](http://rubydoc.info/gems/gitti) 9 | 10 | 11 | 12 | ## Usage 13 | 14 | `Git` • `GitProject` • `GitMirror` 15 | 16 | 17 | ### `Git` Class 18 | 19 | Use the `Git` class for "low-level / to the metal" git commands 20 | that run in your current working directory. 21 | Example: 22 | 23 | ``` ruby 24 | 25 | ############### 26 | ## "setup" starter git commands 27 | 28 | Git.clone( "https://github.com/rubycoco/gitti.git" ) 29 | Git.clone( "https://github.com/rubycoco/gitti.git", "gitti-clone" ) 30 | # -or- -- if you have write / commit access use ssh 31 | Git.clone( "git@github.com:rubycoco/gitti.git" ) 32 | Git.clone( "git@github.com:rubycoco/gitti.git", "gitti-clone" ) 33 | 34 | Git.mirror( "https://github.com/rubycoco/gitti.git" ) ## same as git clone --mirror 35 | 36 | ################# 37 | ## standard git commands 38 | 39 | Git.version ## same as git --version 40 | Git.status 41 | Git.status( short: true ) ## same as Git.changes 42 | Git.changes ## same as git status --short 43 | 44 | ##################### 45 | ## status helpers 46 | 47 | Git.clean? 48 | Git.changes? 49 | Git.dirty? ## alias for changes? 50 | 51 | ####### 52 | ## more (major) git commands 53 | 54 | Git.fetch 55 | Git.pull 56 | Git.fast_forward ## same as git pull --ff-only 57 | Git.ff ## alias for fast_forward 58 | Git.push 59 | Git.add( "pathspec" ) 60 | Git.add_all ## same as git --all 61 | Git.commit( "message" ) 62 | 63 | Git.files ## same as git ls-tree --full-tree --name-only -r HEAD 64 | 65 | Git.check ## same as git fsck 66 | Git.fsck ## alias for check 67 | Git.checksum ## another alias for check 68 | 69 | Git.master? ## on master branch 70 | Git.main? ## on main branch 71 | 72 | Git.origin ## same as git remote show origin 73 | Git.upstream ## same as git remote show upstream 74 | Git.origin? 75 | Git.upstream? 76 | 77 | Git.config( "user.name" ) ## use --get option 78 | Git.config( "user.name", show_origin: true ) ## add --show-origin flag 79 | Git.config( "user.name", show_scope: true ) ## add --show-scope flag 80 | 81 | Git.config( /user/ ) ## use --get-regexp option 82 | Git.config( /user/, show_origin: true ) ## add --show-origin flag 83 | Git.config( /user/, show_scope: true ) ## add --show-scope flag 84 | ``` 85 | 86 | 87 | 88 | ### `GitProject` Class 89 | 90 | Use the `GitProject` class for existing git repo(sitories) 91 | with workspace. Example: 92 | 93 | ``` ruby 94 | GitProject.open( "rubycoco/gitti" ) do |proj| 95 | proj.status 96 | proj.status( short: true ) 97 | proj.changes 98 | proj.clean? 99 | proj.changes? 100 | proj.dirty? 101 | 102 | proj.fetch 103 | proj.pull 104 | proj.fast_forward 105 | proj.ff 106 | 107 | proj.push 108 | 109 | proj.add( "pathspec" ) 110 | proj.add_all 111 | proj.commit( "message" ) 112 | 113 | proj.files 114 | 115 | proj.master? 116 | proj.main? 117 | 118 | proj.origin 119 | proj.upstream 120 | proj.origin? 121 | proj.upstream? 122 | end 123 | ``` 124 | 125 | 126 | ### `GitMirror` Class 127 | 128 | Use the `GitMirror` class for existing mirrored (bare) git repo(sitories) 129 | without workspace. Example: 130 | 131 | ``` ruby 132 | GitMirror.open( "rubycoco/gitti.git" ) do |mirror| 133 | mirror.update # sames as git remote update 134 | end 135 | ``` 136 | 137 | 138 | 139 | That's it for now. 140 | 141 | 142 | 143 | ## Real World Usage 144 | 145 | The [`monos`](https://github.com/rubycoco/monos) gem incl. some monorepo / mono source tree tools and (startup) scripts 146 | that let you run git commands on multiple repos. 147 | 148 | 149 | 150 | ## Installation 151 | 152 | Use 153 | 154 | gem install gitti 155 | 156 | or add to your Gemfile 157 | 158 | gem 'gitti' 159 | 160 | 161 | 162 | ## License 163 | 164 | The `gitti` scripts are dedicated to the public domain. 165 | Use it as you please with no restrictions whatsoever. 166 | 167 | -------------------------------------------------------------------------------- /gitti/Rakefile: -------------------------------------------------------------------------------- 1 | require 'hoe' 2 | require './lib/gitti/version.rb' 3 | 4 | Hoe.spec 'gitti' do 5 | 6 | self.version = GittiCore::VERSION 7 | 8 | self.summary = 'gitti - (yet) another (lite) git command line helper / wrapper' 9 | self.description = summary 10 | 11 | self.urls = { home: 'https://github.com/rubycoco/gitti' } 12 | 13 | self.author = 'Gerald Bauer' 14 | self.email = 'ruby-talk@ruby-lang.org' 15 | 16 | # switch extension to .markdown for gihub formatting 17 | self.readme_file = 'README.md' 18 | self.history_file = 'CHANGELOG.md' 19 | 20 | self.extra_deps = [ 21 | ['shell-lite', '>= 0.0.1' ], 22 | ] 23 | 24 | self.licenses = ['Public Domain'] 25 | 26 | self.spec_extras = { 27 | required_ruby_version: '>= 2.2.2' 28 | } 29 | end 30 | -------------------------------------------------------------------------------- /gitti/lib/gitti.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gitti/base' 2 | 3 | ## note: auto include Gitti; for "modular" version use ("Sinatra-style") 4 | ## require "gitti/base" 5 | 6 | include Gitti 7 | 8 | -------------------------------------------------------------------------------- /gitti/lib/gitti/base.rb: -------------------------------------------------------------------------------- 1 | require 'shell-lite' ## note: move shell execute for (re)use to its own (upstream) gem 2 | 3 | module Gitti 4 | Shell = Computer::Shell 5 | ShellError = Computer::ShellError 6 | GitError = Computer::ShellError ## raised if git exec returns with non-zero exit - just use ShellError - why? why not? 7 | ## raised by Git::Shell.run 8 | ## todo/check: use ShellError or RunError - why? why not? 9 | ## and make Git::Shell top-level e.g. Shell - why? why not? 10 | 11 | ## differentiate into/use 12 | ## GitShell.run/GitCmd.run() or such and Shell.run - why? why not? 13 | end 14 | 15 | 16 | 17 | # our own code 18 | require 'gitti/version' # note: let version always go first 19 | require 'gitti/git' 20 | require 'gitti/project' 21 | require 'gitti/mirror' 22 | require 'gitti/reposet' 23 | 24 | 25 | 26 | module Gitti 27 | ## todo: change to GitHubRepoRef or GitHubProject 28 | ## or Git::GitHub or Git::Source::GitHub or such - why? why not? 29 | class GitHubRepo 30 | attr_reader :owner, :name 31 | 32 | def initialize( owner, name ) 33 | @owner = owner ## use/rename to login or something - why? why not?? 34 | @name = name # e.g. "rubylibs/webservice" 35 | end 36 | 37 | 38 | def ssh_clone_url 39 | ## check: use https: as default? for github - http:// still supported? or redirected? 40 | ## "http://github.com/#{@owner}/#{@name}" 41 | "git@github.com:#{@owner}/#{@name}.git" 42 | end 43 | 44 | def http_clone_url ## use clone_url( http: true ) -- why? why not? 45 | ## note: https is default for github - http:// gets redirected to https:// 46 | "http://github.com/#{@owner}/#{@name}" 47 | end 48 | 49 | def https_clone_url 50 | "https://github.com/#{@owner}/#{@name}" 51 | end 52 | 53 | 54 | end ## class GitHubRepo 55 | end ## module Gitti 56 | 57 | 58 | 59 | # say hello 60 | puts GittiCore.banner ## if defined?( $RUBYCOCO_DEBUG ) 61 | -------------------------------------------------------------------------------- /gitti/lib/gitti/mirror.rb: -------------------------------------------------------------------------------- 1 | module Gitti 2 | 3 | class GitMirror 4 | def self.open( path, &blk ) 5 | new( path ).open( &blk ) 6 | end 7 | 8 | def self.update( path ) ### all-in-one convenience shortcut 9 | new( path).open { |mirror| mirror.update } 10 | end 11 | 12 | 13 | 14 | def initialize( path ) 15 | raise ArgumentError, "dir >#{path}< not found; dir MUST already exist for GitMirror class - sorry" unless Dir.exist?( path ) 16 | ## todo/check: check for more dirs and files e.g. 17 | ## /info,/objects,/refs, /hooks, HEAD, config, description -- why? why not? 18 | raise ArgumentError, "dir >#{path}/objects< not found; dir MUST already be initialized with git for GitMirror class - sorry" unless Dir.exist?( "#{path}/objects" ) 19 | @path = path 20 | end 21 | 22 | 23 | def open( &blk ) 24 | Dir.chdir( @path ) do 25 | blk.call( self ) 26 | end 27 | end 28 | 29 | def update() Git.update; end 30 | 31 | end # class GitMirror 32 | end # module Gitti 33 | 34 | -------------------------------------------------------------------------------- /gitti/lib/gitti/project.rb: -------------------------------------------------------------------------------- 1 | module Gitti 2 | 3 | class GitProject 4 | def self.open( path, &blk ) 5 | new( path ).open( &blk ) 6 | end 7 | 8 | def initialize( path ) 9 | raise ArgumentError, "dir >#{path}< not found; dir MUST already exist for GitProject class - sorry" unless Dir.exist?( path ) 10 | raise ArgumentError, "dir >#{path}/.git< not found; dir MUST already be initialized with git for GitProject class - sorry" unless Dir.exist?( "#{path}/.git" ) 11 | @path = path 12 | end 13 | 14 | 15 | def open( &blk ) 16 | ## puts "Dir.getwd: #{Dir.getwd}" 17 | Dir.chdir( @path ) do 18 | blk.call( self ) 19 | end 20 | ## puts "Dir.getwd: #{Dir.getwd}" 21 | end 22 | 23 | 24 | def status( short: false ) Git.status( short: short ); end 25 | def changes() Git.changes; end 26 | def clean?() Git.clean?; end 27 | def changes?() Git.changes?; end 28 | alias_method :dirty?, :changes? 29 | 30 | 31 | def fetch() Git.fetch; end 32 | def pull() Git.pull; end 33 | def fast_forward() Git.fast_forward; end 34 | alias_method :ff, :fast_forward 35 | 36 | def push() Git.push; end 37 | 38 | def add( *pathspecs ) Git.add( *pathspecs ); end 39 | def add_all() Git.add_all; end 40 | def commit( message ) Git.commit( message ); end 41 | 42 | def files() Git.files; end 43 | 44 | 45 | ### remote show origin|upstream|etc. 46 | def remote() Git.remote; end 47 | def origin() Git.origin; end 48 | def upstream() Git.upstream; end 49 | def origin?() Git.origin?; end 50 | def upstream?() Git.upstream?; end 51 | 52 | ### branch management 53 | def branch() Git.branch; end 54 | def master?() Git.master?; end 55 | def main?() Git.main?; end 56 | 57 | 58 | def run( cmd ) Git::Shell.run( cmd ); end 59 | end # class GitProject 60 | end # module Gitti 61 | 62 | -------------------------------------------------------------------------------- /gitti/lib/gitti/reposet.rb: -------------------------------------------------------------------------------- 1 | 2 | module Gitti 3 | 4 | 5 | class GitRepoSet ## todo: rename to Hash/Dict/List/Map or use GitHubRepoSet ?? 6 | 7 | def self.read( path ) 8 | txt = File.open( path, 'r:utf-8') { |f| f.read } 9 | hash = YAML.load( txt ) 10 | new( hash ) 11 | end 12 | 13 | 14 | def initialize( hash ) 15 | @hash = hash 16 | end 17 | 18 | def size 19 | ## sum up total number of repos 20 | @size ||= @hash.reduce(0) {|sum,(_,names)| sum+= names.size; sum } 21 | end 22 | 23 | def each 24 | @hash.each do |org_with_counter,names| 25 | 26 | ## remove optional number from key e.g. 27 | ## mrhydescripts (3) => mrhydescripts 28 | ## footballjs (4) => footballjs 29 | ## etc. 30 | 31 | org = org_with_counter.sub( /\([0-9]+\)/, '' ).strip 32 | 33 | ## puts " -- #{key_with_counter} [#{key}] --" 34 | 35 | yield( org, names ) 36 | end 37 | end 38 | 39 | end ## class GitRepoSet 40 | 41 | end ## module Gitti 42 | 43 | -------------------------------------------------------------------------------- /gitti/lib/gitti/version.rb: -------------------------------------------------------------------------------- 1 | 2 | ### note: use a different module for version (meta) info 3 | ### that is, GittiCore and NOT Gitti 4 | ### why? do NOT "pollute" Gitti with MAJOR, MINOR, PATH, and 5 | ### self.banner, self.root, etc. 6 | 7 | 8 | module GittiCore ## todo/check: rename GittiBase or GittiMeta or such - why? why not? 9 | MAJOR = 0 ## todo: namespace inside version or something - why? why not?? 10 | MINOR = 6 11 | PATCH = 1 12 | VERSION = [MAJOR,MINOR,PATCH].join('.') 13 | 14 | def self.version 15 | VERSION 16 | end 17 | 18 | def self.banner 19 | "gitti/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" 20 | end 21 | 22 | def self.root 23 | File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) ) 24 | end 25 | end # module GittiCore 26 | 27 | -------------------------------------------------------------------------------- /gitti/sandbox/test_version.rb: -------------------------------------------------------------------------------- 1 | ### 2 | ## check for VERSION if accessible in modules if included 3 | 4 | 5 | module AAA 6 | AAA = "AAA" 7 | end 8 | 9 | module BBB 10 | VERSION = "2.2.BBB" 11 | 12 | 13 | def self.root 14 | puts "BBB.root" 15 | end 16 | end 17 | 18 | 19 | puts BBB::VERSION # 2.2.BBB 20 | 21 | module AAA 22 | include BBB 23 | end 24 | 25 | puts AAA::VERSION # 2.2.BBB 26 | 27 | 28 | module AAA 29 | VERSION = "1.1.AAA" 30 | end 31 | 32 | puts AAA::VERSION # 1.1.AAA 33 | puts BBB::VERSION # 2.2.BBB 34 | 35 | 36 | puts BBB.root 37 | ## puts AAA.root #=> undefined method `root' for AAA:Module (NoMethodError) 38 | 39 | 40 | -------------------------------------------------------------------------------- /gitti/test/helper.rb: -------------------------------------------------------------------------------- 1 | ## minitest setup 2 | require 'minitest/autorun' 3 | 4 | 5 | ## our own code 6 | require 'gitti' 7 | 8 | -------------------------------------------------------------------------------- /gitti/test/test_base.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib -I ./test test/test_base.rb 4 | 5 | require 'helper' 6 | 7 | class TestBase < MiniTest::Test 8 | 9 | def test_branch 10 | Git.branch 11 | assert_equal true, Git.master? 12 | assert_equal false, Git.main? 13 | 14 | Git.remote 15 | assert_equal true, Git.origin? 16 | assert_equal false, Git.upstream? 17 | end 18 | 19 | 20 | def test_git_config 21 | puts "---" 22 | Git.config( 'user.name' ) 23 | Git.config( 'user.name', show_origin: true ) 24 | ## Git.config( 'user.name', show_scope: true ) 25 | 26 | puts "---" 27 | Git.config( /user/ ) ## note: pass in regex for regex match/search 28 | Git.config( /user/, show_origin: true ) 29 | ## Git.config( /user/, show_scope: true ) 30 | 31 | puts "---" 32 | Git.config( /user\./ ) ## note: pass in regex for regex match/search 33 | 34 | puts "---" 35 | ## note: if NOT found Git.config will exit(1) !!! 36 | ## Git.config( /proxy/, show_origin: true ) 37 | ## Git.config( /http/, show_origin: true ) 38 | 39 | puts "---" 40 | end 41 | 42 | end # class TestBase 43 | 44 | -------------------------------------------------------------------------------- /hubba-reports/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /vendor/bundle 26 | /lib/bundler/man/ 27 | 28 | # for a library or gem, you might want to ignore these files since the code is 29 | # intended to run in multiple environments; otherwise, check them in: 30 | # Gemfile.lock 31 | # .ruby-version 32 | # .ruby-gemset 33 | 34 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 35 | .rvmrc 36 | 37 | ### redirected output for testing / debugging 38 | test.txt 39 | orgs.txt 40 | stats.txt 41 | 42 | out.txt 43 | output.txt 44 | 45 | -------------------------------------------------------------------------------- /hubba-reports/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.1 / 2020-10-14 2 | 3 | * Everything is new. First release. 4 | 5 | -------------------------------------------------------------------------------- /hubba-reports/Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | Manifest.txt 3 | README.md 4 | Rakefile 5 | lib/hubba/reports.rb 6 | lib/hubba/reports/folio.rb 7 | lib/hubba/reports/reports/base.rb 8 | lib/hubba/reports/reports/catalog.rb 9 | lib/hubba/reports/reports/languages.rb 10 | lib/hubba/reports/reports/size.rb 11 | lib/hubba/reports/reports/stars.rb 12 | lib/hubba/reports/reports/summary.rb 13 | lib/hubba/reports/reports/timeline.rb 14 | lib/hubba/reports/reports/topics.rb 15 | lib/hubba/reports/reports/traffic.rb 16 | lib/hubba/reports/reports/traffic_pages.rb 17 | lib/hubba/reports/reports/traffic_referrers.rb 18 | lib/hubba/reports/reports/trending.rb 19 | lib/hubba/reports/reports/updates.rb 20 | lib/hubba/reports/stats.rb 21 | lib/hubba/reports/version.rb 22 | test/helper.rb 23 | test/stats/j/jekyll~minima.json 24 | test/stats/o/openblockchains~awesome-blockchains.json 25 | test/stats/o/opendatajson~factbook.json.json 26 | test/stats/p/poole~hyde.json 27 | test/test_stats.rb 28 | test/test_stats_tmp.rb 29 | -------------------------------------------------------------------------------- /hubba-reports/Rakefile: -------------------------------------------------------------------------------- 1 | require 'hoe' 2 | require './lib/hubba/reports/version.rb' 3 | 4 | Hoe.spec 'hubba-reports' do 5 | 6 | self.version = HubbaReports::VERSION 7 | 8 | self.summary = 'hubba-reports - auto-generate github statistics / analytics reports from github api data (stars, timeline, traffic, top pages, top referrers, etc.)' 9 | self.description = summary 10 | 11 | self.urls = { home: 'https://github.com/rubycoco/git' } 12 | 13 | self.author = 'Gerald Bauer' 14 | self.email = 'ruby-talk@ruby-lang.org' 15 | 16 | # switch extension to .markdown for gihub formatting 17 | self.readme_file = 'README.md' 18 | self.history_file = 'CHANGELOG.md' 19 | 20 | self.extra_deps = [ 21 | ['hubba', '>= 1.0.0'] 22 | ] 23 | 24 | self.licenses = ['Public Domain'] 25 | 26 | self.spec_extras = { 27 | required_ruby_version: '>= 2.2.2' 28 | } 29 | 30 | end 31 | -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports.rb: -------------------------------------------------------------------------------- 1 | require 'hubba' 2 | 3 | 4 | ###### 5 | # our own code 6 | require 'hubba/reports/version' # note: let version always go first 7 | require 'hubba/reports/stats' 8 | require 'hubba/reports/folio' 9 | 10 | require 'hubba/reports/reports/base' 11 | require 'hubba/reports/reports/catalog' 12 | require 'hubba/reports/reports/languages' 13 | require 'hubba/reports/reports/size' 14 | require 'hubba/reports/reports/stars' 15 | require 'hubba/reports/reports/summary' 16 | require 'hubba/reports/reports/timeline' 17 | require 'hubba/reports/reports/topics' 18 | require 'hubba/reports/reports/traffic_pages' 19 | require 'hubba/reports/reports/traffic_referrers' 20 | require 'hubba/reports/reports/traffic' 21 | require 'hubba/reports/reports/trending' 22 | require 'hubba/reports/reports/updates' 23 | 24 | 25 | 26 | 27 | module Hubba 28 | def self.stats( hash_or_path='./repos.yml' ) ## use read_stats or such - why? why not? 29 | h = if hash_or_path.is_a?( String ) ## assume it is a file path!!! 30 | path = hash_or_path 31 | YAML.load_file( path ) 32 | else 33 | hash_or_path # assume its a hash / reposet already!!! 34 | end 35 | 36 | Folio.new( h ) ## wrap in "easy-access" facade / wrapper 37 | end ## method stats 38 | end # module Hubba 39 | 40 | 41 | # say hello 42 | puts HubbaReports.banner 43 | 44 | -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports/folio.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | class Folio # todo/check: use a different name e.g (Port)Folio, Cache, Summary, (Data)Base, Census, Catalog, Collection, Index, Register or such??? 4 | class Repo ## (nested) class 5 | 6 | attr_reader :owner, 7 | :name 8 | 9 | def initialize( owner, name ) 10 | @owner = owner ## rename to login, username - why? why not? 11 | @name = name ## rename to reponame ?? 12 | end 13 | 14 | def full_name() "#{owner}/#{name}"; end 15 | 16 | def stats 17 | ## note: load stats on demand only (first access) for now - why? why not? 18 | @stats ||= begin 19 | stats = Stats.new( full_name ) 20 | stats.read 21 | stats 22 | end 23 | end 24 | 25 | def diff 26 | @diff ||= stats.calc_diff_stars( samples: 3, days: 30 ) 27 | end 28 | end # (nested) class Repo 29 | 30 | 31 | attr_reader :orgs, :repos 32 | 33 | def initialize( h ) 34 | @orgs = [] # orgs and users -todo/check: use better name - logins or owners? why? why not? 35 | @repos = [] 36 | add( h ) 37 | 38 | puts "#{@repos.size} repos @ #{@orgs.size} orgs" 39 | end 40 | 41 | ############# 42 | ## private helpes 43 | def add( h ) ## add repos.yml set 44 | h.each do |org_with_counter, names| 45 | ## remove optional number from key e.g. 46 | ## mrhydescripts (3) => mrhydescripts 47 | ## footballjs (4) => footballjs 48 | ## etc. 49 | org = org_with_counter.sub( /\([0-9]+\)/, '' ).strip 50 | repos = [] 51 | names.each do |name| 52 | repo = Repo.new( org, name ) 53 | repos << repo 54 | end 55 | @orgs << [org, repos] 56 | @repos += repos 57 | end 58 | end 59 | end # class Folio 60 | end # module Hubba 61 | -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports/reports/base.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | class Report 4 | def initialize( stats_or_hash_or_path=Hubba.stats ) 5 | ## puts "[debug] Report#initialize:" 6 | ## pp stats_or_hash_or_path if stats_or_hash_or_path.is_a?( String ) 7 | 8 | @stats = if stats_or_hash_or_path.is_a?( String ) || 9 | stats_or_hash_or_path.is_a?( Hash ) 10 | hash_or_path = stats_or_hash_or_path 11 | Hubba.stats( hash_or_path ) 12 | else 13 | stats_or_hash_or_path ## assume Summary/Stats - todo/fix: double check!!! 14 | end 15 | end 16 | 17 | 18 | def save( path ) 19 | buf = build 20 | 21 | banner =<#{path}< ..." 29 | File.open( path, "w:utf-8" ) do |f| 30 | f.write( banner ) ## add banner for now - why? why not? 31 | f.write( buf ) 32 | end 33 | end 34 | end ## class Report 35 | 36 | end # module Hubba -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports/reports/catalog.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | 4 | class ReportCatalog < Report 5 | 6 | def build 7 | 8 | ## note: orgs is orgs+users e.g. geraldb, yorobot etc 9 | buf = String.new('') 10 | buf << "# Catalog" 11 | buf << " - #{@stats.repos.size} Repos @ #{@stats.orgs.size} Orgs" 12 | buf << "\n\n" 13 | 14 | 15 | @stats.orgs.each do |org| 16 | name = org[0] 17 | repos = org[1] 18 | buf << "### #{name} _(#{repos.size})_\n" 19 | buf << "\n" 20 | 21 | ### add stats for repos 22 | repos.each do |repo| 23 | 24 | buf << "**#{repo.name}** ★#{repo.stats.stars} (#{repo.stats.size} kb)" 25 | buf << "
\n" 26 | 27 | buf << "_#{repo.stats.description}_" 28 | 29 | if repo.stats.topics && repo.stats.topics.size > 0 30 | buf << "
\n" 31 | ## wrap in backtip (verbatim code) 32 | buf << repo.stats.topics.map {|topic| "`#{topic}`" }.join( ' · ' ) ## use interpunct? - was: • (bullet) 33 | end 34 | buf << "\n\n" 35 | end 36 | 37 | buf << "\n" 38 | end 39 | 40 | buf 41 | end # method build 42 | end # class ReportCatalog 43 | 44 | end # module Hubba -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports/reports/languages.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | 4 | class ReportLanguages < Report 5 | 6 | def build 7 | 8 | ## note: orgs is orgs+users e.g. geraldb, yorobot etc 9 | buf = String.new('') 10 | buf << "# Languages" 11 | buf << " - #{@stats.repos.size} Repos @ #{@stats.orgs.size} Orgs" 12 | buf << "\n\n" 13 | 14 | 15 | buf << "language lines in byte / language used in repos (count)" 16 | buf << "\n\n" 17 | 18 | 19 | ### note: skip repos WITHOUT languages stats e.g. empty hash {} 20 | repos = @stats.repos.select do |repo| 21 | langs = repo.stats.languages 22 | res = langs && langs.size > 0 ## return true if present and non-empty hash too 23 | puts " no languages - skipping >#{repo.full_name}<..." unless res 24 | res 25 | end 26 | 27 | 28 | ## collect all langs with refs to repos 29 | ## note/warn: do NOT use langs as local variable - will RESET this langs here!!! 30 | langs = {} 31 | 32 | repos.each do |repo| 33 | puts "#{repo.full_name}:" 34 | pp repo.stats.languages 35 | repo.stats.languages.each do |lang,bytes| 36 | # note: keep using all string (NOT symbol) keys - why? why not? 37 | line = langs[ lang ] ||= { 'count' => 0, 'bytes' => 0 } 38 | line[ 'count' ] += 1 39 | line[ 'bytes' ] += bytes 40 | end 41 | end 42 | 43 | langs_by_bytes = langs.sort {|(llang,lline),(rlang,rline)| 44 | res = rline['bytes'] <=> lline['bytes'] 45 | res = llang <=> rlang if res == 0 46 | res 47 | } 48 | .to_h # convert back to hash (from array) 49 | 50 | 51 | bytes_total = langs.reduce(0) {|sum,(lang,line)| sum+line['bytes'] } 52 | langs_by_bytes.each_with_index do |(lang, line),i| 53 | bytes = line['bytes'] 54 | percent = Float(100*bytes)/Float(bytes_total) 55 | 56 | buf << "#{i+1}. " 57 | buf << "#{bytes} (#{('%2.2f' % percent)}%) " 58 | buf << "**#{lang}** " 59 | buf << "_(#{line['count']})_" 60 | buf << "\n" 61 | end 62 | buf << "\n" ## markdown hack: add a list end marker 63 | buf << "\n\n" 64 | 65 | 66 | buf << "Languages used in repos (count):" 67 | buf << "\n\n" 68 | 69 | 70 | langs_by_count = langs.sort {|(llang,lline),(rlang,rline)| 71 | res = rline['count'] <=> lline['count'] 72 | res = llang <=> rlang if res == 0 73 | res 74 | } 75 | .to_h # convert back to hash (from array) 76 | 77 | 78 | count_total = repos.size # note: use (filtered) repos for count total 79 | langs_by_count.each_with_index do |(lang, line),i| 80 | count = line['count'] 81 | percent = Float(100*count)/Float(count_total) 82 | 83 | buf << "#{i+1}. " 84 | buf << "#{count} (#{('%2.2f' % percent)}%) " 85 | buf << "**#{lang}** " 86 | buf << "(#{line['bytes']} bytes)" 87 | buf << "\n" 88 | end 89 | buf << "\n" ## markdown hack: add a list end marker 90 | buf << "\n\n" 91 | 92 | 93 | buf 94 | end # method build 95 | end # class ReportLanguages 96 | 97 | end # module Hubba -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports/reports/size.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | 4 | class ReportSize < Report 5 | 6 | def build 7 | 8 | ## add stars, last_updates, etc. 9 | ## org description etc?? 10 | 11 | ## note: orgs is orgs+users e.g. geraldb, yorobot etc 12 | buf = String.new('') 13 | buf << "# Size in KB" 14 | buf << " - #{@stats.repos.size} Repos @ #{@stats.orgs.size} Orgs" 15 | buf << "\n\n" 16 | 17 | 18 | repos = @stats.repos.sort do |l,r| 19 | ## note: use reverse sort (right,left) - e.g. most stars first 20 | res = r.stats.size <=> l.stats.size 21 | res = l.full_name <=> r.full_name if res == 0 22 | res 23 | end 24 | 25 | 26 | repos.each_with_index do |repo,i| 27 | buf << "#{i+1}. #{repo.stats.size} kb - **#{repo.full_name}**\n" 28 | end 29 | buf << "\n" ## markdown hack: add a list end marker 30 | buf << "\n\n" 31 | 32 | 33 | buf 34 | end # method build 35 | end # class ReportSize 36 | 37 | 38 | end # module Hubba 39 | -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports/reports/stars.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | 4 | class ReportStars < Report 5 | 6 | def build 7 | 8 | ## add stars, last_updates, etc. 9 | ## org description etc?? 10 | 11 | ## note: orgs is orgs+users e.g. geraldb, yorobot etc 12 | buf = String.new('') 13 | buf << "# Stars" 14 | buf << " - #{@stats.repos.size} Repos @ #{@stats.orgs.size} Orgs" 15 | buf << "\n\n" 16 | 17 | 18 | repos = @stats.repos.sort do |l,r| 19 | ## note: use reverse sort (right,left) - e.g. most stars first 20 | r.stats.stars <=> l.stats.stars 21 | end 22 | 23 | ## pp repos 24 | 25 | repos.each_with_index do |repo,i| 26 | buf << "#{i+1}. ★#{repo.stats.stars} **#{repo.full_name}** (#{repo.stats.size} kb)\n" 27 | end 28 | buf << "\n" ## markdown hack: add a list end marker 29 | buf << "\n\n" 30 | 31 | 32 | buf 33 | end # method build 34 | end # class ReportStars 35 | 36 | 37 | end # module Hubba 38 | -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports/reports/summary.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | 4 | class ReportSummary < Report 5 | 6 | def build 7 | ## create a (summary report) 8 | ## 9 | ## add stars, last_updates, etc. 10 | ## org description etc?? 11 | 12 | ## note: orgs is orgs+users e.g. geraldb, yorobot etc 13 | buf = String.new('') 14 | buf << "# Summary" 15 | buf << " - #{@stats.repos.size} Repos @ #{@stats.orgs.size} Orgs" 16 | buf << "\n\n" 17 | 18 | 19 | @stats.orgs.each do |org| 20 | name = org[0] 21 | repos = org[1] 22 | buf << "### #{name} _(#{repos.size})_\n" 23 | buf << "\n" 24 | 25 | ### add stats for repos 26 | entries = [] 27 | repos.each do |repo| 28 | entries << "**#{repo.name}** ★#{repo.stats.stars} (#{repo.stats.size} kb)" 29 | end 30 | 31 | buf << entries.join( ' · ' ) ## use interpunct? - was: • (bullet) 32 | buf << "\n\n" 33 | end 34 | 35 | buf 36 | end # method build 37 | end # class ReportSummary 38 | 39 | end # module Hubba -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports/reports/timeline.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | 4 | class ReportTimeline < Report 5 | 6 | def build 7 | ## create a (timeline report) 8 | 9 | ## note: orgs is orgs+users e.g. geraldb, yorobot etc 10 | buf = String.new('') 11 | buf << "# Timeline" 12 | buf << " - #{@stats.repos.size} Repos @ #{@stats.orgs.size} Orgs" 13 | buf << "\n\n" 14 | 15 | 16 | repos = @stats.repos.sort do |l,r| 17 | ## note: use reverse sort (right,left) - e.g. most stars first 18 | ## r[:stars] <=> l[:stars] 19 | 20 | ## sort by created_at (use julian days) 21 | r.stats.created.jd <=> l.stats.created.jd 22 | end 23 | 24 | 25 | ## pp repos 26 | 27 | 28 | last_year = -1 29 | last_month = -1 30 | 31 | repos.each_with_index do |repo,i| 32 | year = repo.stats.created.year 33 | month = repo.stats.created.month 34 | 35 | if last_year != year 36 | buf << "\n## #{year}\n\n" 37 | end 38 | 39 | if last_month != month 40 | buf << "\n### #{month}\n\n" 41 | end 42 | 43 | last_year = year 44 | last_month = month 45 | 46 | buf << "- #{repo.stats.created_at.strftime('%Y-%m-%d')} ★#{repo.stats.stars} **#{repo.full_name}** (#{repo.stats.size} kb)\n" 47 | end 48 | 49 | buf 50 | end # method build 51 | end # class ReportTimeline 52 | 53 | end # module Hubba 54 | -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports/reports/topics.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | 4 | class ReportTopics < Report 5 | 6 | def build 7 | 8 | ## note: orgs is orgs+users e.g. geraldb, yorobot etc 9 | buf = String.new('') 10 | buf << "# Topics" 11 | buf << " - #{@stats.repos.size} Repos @ #{@stats.orgs.size} Orgs" 12 | buf << "\n\n" 13 | 14 | 15 | topics = {} ## collect all topics with refs to repos 16 | 17 | @stats.repos.each do |repo| 18 | repo.stats.topics.each do |topic| 19 | repos = topics[ topic ] ||= [] 20 | repos << repo 21 | end 22 | end 23 | 24 | topics = topics.sort {|(ltopic,_),(rtopic,_)| 25 | ltopic <=> rtopic ## sort topic by a-z 26 | } 27 | .to_h # convert back to hash (from array) 28 | 29 | 30 | topics.each do |topic,repos| 31 | buf << "`#{topic}` _(#{repos.size})_\n" 32 | end 33 | buf << "\n" 34 | 35 | 36 | topics.each do |topic,repos| 37 | buf << "## `#{topic}` _(#{repos.size})_\n" 38 | 39 | buf << repos.map {|repo| repo.full_name }.join( ' · ' ) ## use interpunct? - was: • (bullet) 40 | buf << "\n\n" 41 | end 42 | 43 | 44 | buf 45 | end # method build 46 | end # class ReportTopics 47 | 48 | end # module Hubba -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports/reports/traffic_referrers.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | 4 | class ReportTrafficReferrers < Report 5 | 6 | def build 7 | 8 | ## note: orgs is orgs+users e.g. geraldb, yorobot etc 9 | buf = String.new('') 10 | buf << "# Traffic Referrers" 11 | buf << " - #{@stats.repos.size} Repos @ #{@stats.orgs.size} Orgs" 12 | buf << "\n\n" 13 | 14 | 15 | buf << "popular referrer sources over the last 14 days - page views / unique\n" 16 | buf << "\n" 17 | 18 | 19 | ### step 1: filter all repos w/o traffic summary 20 | repos = @stats.repos.select do |repo| 21 | traffic = repo.stats.traffic || {} 22 | summary = traffic['summary'] || {} 23 | 24 | referrers = summary['referrers'] 25 | res = referrers && referrers.size > 0 ## return true if present and non-empty array too 26 | puts " no traffic/summary/referrers - skipping >#{repo.full_name}<..." unless res 27 | res 28 | end 29 | 30 | ## collect all referrers entries 31 | lines = [] 32 | repos.each do |repo| 33 | summary = repo.stats.traffic['summary'] 34 | # e.g. 35 | # "referrers" => 36 | # [{"referrer"=>"github.com", "count"=>327, "uniques"=>198}, 37 | # {"referrer"=>"openfootball.github.io", "count"=>71, "uniques"=>54}, 38 | # {"referrer"=>"Google", "count"=>5, "uniques"=>5}, 39 | # {"referrer"=>"reddit.com", "count"=>4, "uniques"=>4}] 40 | 41 | 42 | referrers = summary['referrers'] 43 | if referrers 44 | lines += referrers.map do |referrer| 45 | # note: return a new copy with (repo) path added 46 | referrer.merge( 'path' => repo.full_name ) 47 | end 48 | end 49 | end 50 | 51 | 52 | 53 | ## sort by 1) count 54 | ## 2) uniques 55 | ## 3) a-z referrer 56 | ## 4) a-z path 57 | lines = lines.sort do |l,r| 58 | res = r['count'] <=> l['count'] 59 | res = r['uniques'] <=> l['uniques'] if res == 0 60 | res = l['referrer'] <=> r['referrer'] if res == 0 61 | res = l['path'] <=> r['path'] if res == 0 62 | res 63 | end 64 | 65 | 66 | lines_by_referrer = lines.group_by { |line| line['referrer'] } 67 | .sort { |(lreferrer,llines), 68 | (rreferrer,rlines)| 69 | lcount = llines.reduce(0) {|sum,line| sum+line['count'] } 70 | rcount = rlines.reduce(0) {|sum,line| sum+line['count'] } 71 | res = rcount <=> lcount 72 | res = llines.size <=> rlines.size if res == 0 73 | res = lreferrer <=> rreferrer if res == 0 74 | res 75 | } 76 | .to_h ## convert back to hash 77 | 78 | 79 | ### start with summary 80 | ## limit to top 10 or top 20 - why? why not? 81 | lines_by_referrer.each_with_index do |(referrer, lines),i| 82 | count = lines.reduce(0) {|sum,line| sum+line['count']} 83 | buf << "#{i+1}. **#{referrer}** #{count} _(#{lines.size})_" 84 | buf << "\n" 85 | end 86 | 87 | buf << "\n" ## markdown hack: add a list end marker 88 | buf << "\n\n" 89 | 90 | 91 | 92 | buf << "Details:" 93 | buf << "\n\n" 94 | 95 | 96 | lines_by_referrer.each_with_index do |(referrer, lines),i| 97 | count = lines.reduce(0) {|sum,line| sum+line['count']} 98 | buf << "#{i+1}. **#{referrer}** #{count} _(#{lines.size})_" 99 | buf << "\n" 100 | 101 | ### todo - sort by count / uniques !! 102 | lines.each do |line| 103 | ## note: sublist indent four (4) spaces 104 | buf << " - #{line['count']} / #{line['uniques']} -- #{line['path']}" 105 | buf << "\n" 106 | end 107 | end 108 | 109 | buf << "\n" ## markdown hack: add a list end marker 110 | buf << "\n\n" 111 | 112 | 113 | ### all referrer sources / records by page views 114 | buf << "All referrers:" 115 | buf << "\n\n" 116 | 117 | lines.each_with_index do |line,i| 118 | buf << "- #{line['referrer']} -- #{line['count']} / #{line['uniques']} -- #{line['path']}" 119 | buf << "\n" 120 | end 121 | 122 | buf << "\n" ## markdown hack: add a list end marker 123 | buf << "\n\n" 124 | 125 | 126 | 127 | buf 128 | end # method build 129 | end # class ReportTrafficReferrers 130 | 131 | end # module Hubba 132 | -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports/reports/trending.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | class ReportTrending < Report 4 | 5 | def build 6 | 7 | ## note: orgs is orgs+users e.g. geraldb, yorobot etc 8 | buf = String.new('') 9 | buf << "# Trending" 10 | buf << " - #{@stats.repos.size} Repos @ #{@stats.orgs.size} Orgs" 11 | buf << "\n\n" 12 | 13 | ### 14 | ## todo: 15 | ## use calc per month (days: 30) 16 | ## per week is too optimistic (e.g. less than one star/week e.g. 0.6 or something) 17 | 18 | repos = @stats.repos.sort do |l,r| 19 | ## note: use reverse sort (right,left) - e.g. most stars first 20 | ## r[:stars] <=> l[:stars] 21 | 22 | ## sort by created_at (use julian days) 23 | ## r[:created_at].to_date.jd <=> l[:created_at].to_date.jd 24 | 25 | res = r.diff <=> l.diff 26 | res = r.stats.stars <=> l.stats.stars if res == 0 27 | res = r.stats.created.jd <=> l.stats.created.jd if res == 0 28 | res 29 | end 30 | 31 | 32 | ## pp repos 33 | 34 | 35 | repos.each_with_index do |repo,i| 36 | if repo.diff == 0 37 | buf << "- -/- " 38 | else 39 | buf << "- #{repo.diff}/month " 40 | end 41 | 42 | buf << " ★#{repo.stats.stars} **#{repo.full_name}** (#{repo.stats.size} kb) - " 43 | buf << "#{repo.stats.history_str}\n" 44 | end 45 | 46 | 47 | buf 48 | end # method build 49 | end # class ReportTrending 50 | 51 | end # module Hubba -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports/reports/updates.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | 4 | class ReportUpdates < Report 5 | 6 | def build 7 | 8 | ## note: orgs is orgs+users e.g. geraldb, yorobot etc 9 | buf = String.new('') 10 | buf << "# Updates" 11 | buf << " - #{@stats.repos.size} Repos @ #{@stats.orgs.size} Orgs\n" 12 | buf << "\n\n" 13 | 14 | repos = @stats.repos.sort do |l,r| 15 | r.stats.committed.jd <=> l.stats.committed.jd 16 | end 17 | 18 | ## pp repos 19 | 20 | 21 | buf << "committed / pushed / updated / created\n\n" 22 | 23 | today = Date.today 24 | 25 | repos.each_with_index do |repo,i| 26 | 27 | days_ago = today.jd - repo.stats.committed.jd 28 | 29 | diff1 = repo.stats.committed.jd - repo.stats.pushed.jd 30 | diff2 = repo.stats.committed.jd - repo.stats.updated.jd 31 | diff3 = repo.stats.pushed.jd - repo.stats.updated.jd 32 | 33 | buf << "- (#{days_ago}d) **#{repo.full_name}** ★#{repo.stats.stars} - " 34 | buf << "#{repo.stats.committed} " 35 | buf << "(" 36 | buf << (diff1==0 ? '=' : "#{diff1}d") 37 | buf << "/" 38 | buf << (diff2==0 ? '=' : "#{diff2}d") 39 | buf << ")" 40 | buf << " / " 41 | buf << "#{repo.stats.pushed} " 42 | buf << "(" 43 | buf << (diff3==0 ? '=' : "#{diff3}d") 44 | buf << ")" 45 | buf << " / " 46 | buf << "#{repo.stats.updated} / " 47 | buf << "#{repo.stats.created} - " 48 | buf << "‹#{repo.stats.last_commit_message}›" 49 | buf << " (#{repo.stats.size} kb)" 50 | buf << "\n" 51 | end 52 | buf << "\n" ## markdown hack: add a list end marker 53 | buf << "\n\n" 54 | 55 | 56 | buf 57 | end # method build 58 | end # class ReportUpdates 59 | 60 | end # module Hubba -------------------------------------------------------------------------------- /hubba-reports/lib/hubba/reports/version.rb: -------------------------------------------------------------------------------- 1 | module HubbaReports 2 | MAJOR = 1 ## todo: namespace inside version or something - why? why not?? 3 | MINOR = 0 4 | PATCH = 1 5 | VERSION = [MAJOR,MINOR,PATCH].join('.') 6 | 7 | def self.version 8 | VERSION 9 | end 10 | 11 | def self.banner 12 | "hubba-reports/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" 13 | end 14 | 15 | def self.root 16 | File.expand_path( File.dirname(File.dirname(File.dirname(File.dirname(__FILE__)))) ) 17 | end 18 | end # module HubbaReports 19 | -------------------------------------------------------------------------------- /hubba-reports/test/helper.rb: -------------------------------------------------------------------------------- 1 | # minitest setup 2 | require 'minitest/autorun' 3 | 4 | 5 | ## note: also use local version of hubba!!! 6 | $LOAD_PATH.unshift( "../hubba/lib" ) 7 | 8 | 9 | ## our own code 10 | require 'hubba/reports' 11 | 12 | -------------------------------------------------------------------------------- /hubba-reports/test/stats/j/jekyll~minima.json: -------------------------------------------------------------------------------- 1 | { 2 | "full_name": "jekyll/minima", 3 | "created_at": "2016-05-20T23:07:56Z", 4 | "updated_at": "2018-02-11T16:13:33Z", 5 | "pushed_at": "2018-02-07T22:14:11Z", 6 | "size": 321, 7 | "history": { 8 | "2018-02-12": { 9 | "stargazers_count": 717 10 | } 11 | }, 12 | "commits": [ 13 | { 14 | "author": { 15 | "name": "ashmaroli", 16 | "date": "2018-02-21T19:35:59Z" 17 | }, 18 | "committer": { 19 | "name": "Frank Taillandier", 20 | "date": "2018-02-21T19:35:59Z" 21 | }, 22 | "message": "social icons should resolve baseurl properly (#201)" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /hubba-reports/test/stats/o/openblockchains~awesome-blockchains.json: -------------------------------------------------------------------------------- 1 | { 2 | "full_name": "openblockchains/awesome-blockchains", 3 | "created_at": "2017-09-13T22:33:56Z", 4 | "size": 1620, 5 | "history": { 6 | "2017-12-10": { 7 | "stargazers_count": 1084 8 | }, 9 | "2018-01-28": { 10 | "stargazers_count": 1411 11 | }, 12 | "2018-02-08": { 13 | "stargazers_count": 1526 14 | } 15 | }, 16 | "commits": [ 17 | { 18 | "committer": { 19 | "date": "2018-02-08T09:33:28Z", 20 | "name": "Gerald Bauer" 21 | }, 22 | "message": "Update README.md\n\nJust a little typo cryto -> crypto." 23 | } 24 | ], 25 | "updated_at": "2018-02-08T19:26:35Z", 26 | "pushed_at": "2018-02-08T09:33:29Z" 27 | } -------------------------------------------------------------------------------- /hubba-reports/test/stats/o/opendatajson~factbook.json.json: -------------------------------------------------------------------------------- 1 | { 2 | "full_name": "opendatajson/factbook.json", 3 | "created_at": "2014-07-12T12:43:52Z", 4 | "history": { 5 | "2017-02-11": { 6 | "stargazers_count": 457 7 | }, 8 | "2017-02-12": { 9 | "stargazers_count": 457 10 | }, 11 | "2017-06-18": { 12 | "stargazers_count": 505 13 | }, 14 | "2017-07-28": { 15 | "stargazers_count": 512 16 | }, 17 | "2017-12-10": { 18 | "stargazers_count": 533 19 | }, 20 | "2018-01-28": { 21 | "stargazers_count": 536 22 | }, 23 | "2018-02-08": { 24 | "stargazers_count": 539 25 | } 26 | }, 27 | "commits": [ 28 | { 29 | "committer": { 30 | "date": "2017-03-29T17:23:29Z", 31 | "name": "GitHub" 32 | }, 33 | "message": "Update MONGO.md" 34 | } 35 | ], 36 | "size": 7355, 37 | "updated_at": "2018-02-01T12:35:19Z", 38 | "pushed_at": "2017-03-29T17:23:30Z" 39 | } -------------------------------------------------------------------------------- /hubba-reports/test/stats/p/poole~hyde.json: -------------------------------------------------------------------------------- 1 | { 2 | "full_name": "poole/hyde", 3 | "created_at": "2013-02-07T07:01:38Z", 4 | "updated_at": "2018-02-12T05:44:07Z", 5 | "pushed_at": "2018-01-14T02:41:16Z", 6 | "size": 28428, 7 | "history": { 8 | "2018-02-12": { 9 | "stargazers_count": 2125 10 | } 11 | }, 12 | "commits": [ 13 | { 14 | "committer": { 15 | "date": "2015-05-11T20:21:43Z", 16 | "name": "Mark Otto" 17 | }, 18 | "message": "Merge pull request #91 from pborreli/patch-1\n\nFixed typo" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /hubba-reports/test/test_stats.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib -I ./test test/test_stats.rb 4 | 5 | 6 | require 'helper' 7 | 8 | 9 | class TestStats < MiniTest::Test 10 | 11 | def test_jekyll_minima 12 | stats = Hubba::Stats.new( 'jekyll/minima' ) 13 | 14 | assert_equal 0, stats.size 15 | assert_equal 0, stats.stars 16 | assert_nil stats.history 17 | 18 | Hubba.config.data_dir = "#{HubbaReports.root}/test/stats" 19 | stats.read 20 | 21 | assert_equal 321, stats.size 22 | assert_equal 717, stats.stars 23 | assert_equal 717, stats.history[0].stars 24 | assert_equal 1, stats.history.size 25 | 26 | assert_equal Date.new(2018, 2, 12 ), stats.history[0].date 27 | 28 | assert_nil stats.history[0].diff_days 29 | 30 | assert_equal Date.new(2018, 2, 21 ), stats.committed 31 | assert_equal Date.new(2016, 5, 20 ), stats.created 32 | assert_equal Date.new(2018, 2, 11 ), stats.updated 33 | assert_equal Date.new(2018, 2, 7 ), stats.pushed 34 | 35 | assert_equal DateTime.new(2018, 2, 21, 19, 35, 59 ), stats.committed_at 36 | assert_equal DateTime.new(2016, 5, 20, 23, 7, 56 ), stats.created_at 37 | assert_equal DateTime.new(2018, 2, 11, 16, 13, 33 ), stats.updated_at 38 | assert_equal DateTime.new(2018, 2, 7, 22, 14, 11 ), stats.pushed_at 39 | 40 | 41 | pp stats.last_commit 42 | pp stats.last_commit_message 43 | pp stats.history_str ## pp history pretty printed to string (buffer) 44 | end 45 | 46 | 47 | 48 | def test_awesome_blockchains 49 | 50 | stats = Hubba::Stats.new( 'openblockchains/awesome-blockchains' ) 51 | 52 | assert_equal 0, stats.size 53 | assert_equal 0, stats.stars 54 | assert_nil stats.history 55 | 56 | Hubba.config.data_dir = "#{HubbaReports.root}/test/stats" 57 | stats.read 58 | 59 | assert_equal 1620, stats.size 60 | assert_equal 1526, stats.stars 61 | assert_equal 1526, stats.history[0].stars 62 | assert_equal 1411, stats.history[1].stars 63 | assert_equal 1084, stats.history[2].stars 64 | assert_equal 1084, stats.history[-1].stars 65 | assert_equal 3, stats.history.size 66 | 67 | assert_equal Date.new(2018, 2, 8 ), stats.history[0].date 68 | assert_equal Date.new(2018, 1, 28 ), stats.history[1].date 69 | assert_equal Date.new(2017, 12, 10 ), stats.history[2].date 70 | 71 | assert_equal 11, stats.history[0].diff_days 72 | assert_equal 49, stats.history[1].diff_days 73 | assert_nil stats.history[2].diff_days 74 | 75 | assert_equal 115, stats.history[0].diff_stars 76 | assert_equal 327, stats.history[1].diff_stars 77 | assert_nil stats.history[2].diff_stars 78 | 79 | assert_equal 221.0, stats.calc_diff_stars ## defaults to samples: 3, days: 30 80 | assert_equal 51.566, stats.calc_diff_stars( samples: 5, days: 7 ) 81 | 82 | pp stats.history_str ## pp history pretty printed to string (buffer) 83 | end 84 | 85 | 86 | def test_factbook_json 87 | 88 | stats = Hubba::Stats.new( 'opendatajson/factbook.json' ) 89 | 90 | assert_equal 0, stats.size 91 | assert_equal 0, stats.stars 92 | assert_nil stats.history 93 | 94 | Hubba.config.data_dir = "#{HubbaReports.root}/test/stats" 95 | stats.read 96 | 97 | assert_equal 7355, stats.size 98 | assert_equal 539, stats.stars 99 | assert_equal 539, stats.history[0].stars 100 | assert_equal 536, stats.history[1].stars 101 | assert_equal 533, stats.history[2].stars 102 | assert_equal 457, stats.history[-1].stars 103 | assert_equal 7, stats.history.size 104 | 105 | assert_equal Date.new(2018, 2, 8 ), stats.history[0].date 106 | assert_equal Date.new(2018, 1, 28 ), stats.history[1].date 107 | assert_equal Date.new(2017, 12, 10 ), stats.history[2].date 108 | 109 | assert_equal 11, stats.history[0].diff_days 110 | assert_equal 49, stats.history[1].diff_days 111 | assert_nil stats.history[-1].diff_days 112 | 113 | assert_equal 3, stats.history[0].diff_stars 114 | assert_equal 3, stats.history[1].diff_stars 115 | assert_nil stats.history[-1].diff_stars 116 | 117 | assert_equal 3.0, stats.calc_diff_stars ## defaults to samples: 3, days: 30 118 | assert_equal 1.012, stats.calc_diff_stars( samples: 5, days: 7 ) 119 | 120 | pp stats.history_str ## pp history pretty printed to string (buffer) 121 | end 122 | 123 | end # class TestStats 124 | -------------------------------------------------------------------------------- /hubba-reports/test/test_stats_tmp.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib -I ./test test/test_stats_tmp.rb 4 | 5 | 6 | require 'helper' 7 | 8 | 9 | class TestStatsTmp < MiniTest::Test 10 | 11 | def setup 12 | @gh = Hubba::Github.new 13 | end 14 | 15 | def test_stats 16 | repos = [ 17 | 'poole/hyde', 18 | 'jekyll/minima' 19 | ] 20 | 21 | 22 | repos.each do |repo| 23 | stats = Hubba::Stats.new( repo ) 24 | 25 | Hubba.config.data_dir = "#{HubbaReports.root}/test/stats" 26 | stats.read() 27 | 28 | puts "stars before fetch: #{stats.stars}" 29 | puts "size before fetch: #{stats.size} kb" 30 | 31 | ## note/todo: enable for "live" online testing 32 | ## @gh.update( stats ) 33 | 34 | puts "stars after fetch: #{stats.stars}" 35 | puts "size after fetch: #{stats.size} kb" 36 | 37 | Hubba.config.data_dir = './tmp' 38 | stats.write() 39 | end 40 | 41 | assert true # for now everything ok if we get here 42 | end 43 | 44 | end # class TestStatsTmp 45 | -------------------------------------------------------------------------------- /hubba/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /vendor/bundle 26 | /lib/bundler/man/ 27 | 28 | # for a library or gem, you might want to ignore these files since the code is 29 | # intended to run in multiple environments; otherwise, check them in: 30 | # Gemfile.lock 31 | # .ruby-version 32 | # .ruby-gemset 33 | 34 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 35 | .rvmrc 36 | 37 | ### redirected output for testing / debugging 38 | test.txt 39 | orgs.txt 40 | stats.txt 41 | 42 | out.txt 43 | output.txt 44 | 45 | -------------------------------------------------------------------------------- /hubba/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 1.0.3 2 | ### 0.0.1 / 2015-11-10 3 | 4 | * Everything is new. First release. 5 | 6 | -------------------------------------------------------------------------------- /hubba/Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | Manifest.txt 3 | README.md 4 | Rakefile 5 | lib/hubba.rb 6 | lib/hubba/config.rb 7 | lib/hubba/github.rb 8 | lib/hubba/reposet.rb 9 | lib/hubba/stats.rb 10 | lib/hubba/update.rb 11 | lib/hubba/update_traffic.rb 12 | lib/hubba/version.rb 13 | test/helper.rb 14 | test/test_config.rb 15 | -------------------------------------------------------------------------------- /hubba/NOTES.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | ## Todos 4 | 5 | - [] check Sawyer::Resource ?? 6 | - https://github.com/lostisland/sawyer/blob/master/lib/sawyer/resource.rb - use for dot access - why? why not? 7 | - [ ] add auto-pagination; check for pagination headers 8 | - [ ] add rate limit - check for rate limit headers too - why? why not? 9 | 10 | 11 | 12 | 13 | - [ ] add / keep track of api calls and remaining calls / limits 14 | - [ ] see for printing ??? 15 | - [ ] add counter [1/200] for update_stats and update_traffic!!!! 16 | 17 | 18 | 19 | 20 | - [ ] move Cache to attic - no longer use 21 | - [ ] fix in Webclient - GET URL debug output - split in HOSTNAME and GET request_uri or such!!! 22 | - [ ] check stats / history item - add a little docu - why? why not? 23 | 24 | 25 | --- 26 | 27 | check: 28 | 29 | in repos data 30 | - "private": false 31 | - "fork": false 32 | - "description": "open data and scripts for austria (österreich)" 33 | - "size": 552, 34 | - "stargazers_count": 1, 35 | - "forks_count": 0, 36 | - "forks": 0, 37 | - ... 38 | - "owner": { 39 | "login": "geraldb", 40 | "type": "User", 41 | "site_admin": false 42 | } 43 | 44 | 45 | 46 | ## Rename hubba? 47 | 48 | available: 49 | - hubb ? 50 | - huub (dutch) ? 51 | - hubard 52 | - hubertus 53 | - ... 54 | 55 | NOT available: 56 | - hub / hubbard / hubble / hubert - already taken on rubygems 57 | 58 | 59 | ## Rename cache.github repo for data store? 60 | 61 | - github.data ? 62 | - github.analytics ? 63 | - cache.github ? 64 | 65 | 66 | 67 | ## Resources 68 | 69 | more github analytics projects 70 | 71 | - 72 | - 73 | - 74 | - 75 | - 76 | - 77 | - 78 | 79 | 80 | more github api projects 81 | 82 | - 83 | 84 | python 85 | 86 | - 87 | - 88 | 89 | 90 | 91 | 92 | 93 | official github api docs 94 | 95 | 96 | _Traffic - For repositories that you have push access to, the traffic API provides access to the information provided in your repository graph_ 97 | 98 | - 99 | - 100 | - 101 | 102 | 103 | 104 | misc 105 | 106 | - 107 | - 108 | - 109 | - 110 | - 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /hubba/README.md: -------------------------------------------------------------------------------- 1 | # hubba 2 | 3 | hubba gem - (yet) another (lite) GitHub HTTP API client / library 4 | 5 | * home :: [github.com/rubycoco/git](https://github.com/rubycoco/git) 6 | * bugs :: [github.com/rubycoco/git/issues](https://github.com/rubycoco/git/issues) 7 | * gem :: [rubygems.org/gems/hubba](https://rubygems.org/gems/hubba) 8 | * rdoc :: [rubydoc.info/gems/hubba](http://rubydoc.info/gems/hubba) 9 | 10 | 11 | ## Usage 12 | 13 | 14 | ### Step 0: Secrets, Secrets, Secrets - Your Authentication Token 15 | 16 | Note: Set your GitHub env credentials (personal access token preferred) e.g. 17 | 18 | ``` 19 | set HUBBA_TOKEN=abcdef0123456789 20 | # - or - 21 | set HUBBA_USER=you 22 | set HUBBA_PASSWORD=topsecret 23 | ``` 24 | 25 | 26 | ### Step 1: Get a list of all your repos 27 | 28 | Use the GitHub API to get a list of all your repos: 29 | 30 | ``` ruby 31 | require 'hubba' 32 | 33 | h = Hubba.reposet( 'geraldb' ) 34 | pp h 35 | 36 | File.open( './repos.yml', 'w' ) { |f| f.write( h.to_yaml ) } 37 | ``` 38 | 39 | resulting in: 40 | 41 | ``` yaml 42 | geraldb (11): 43 | - austria 44 | - catalog 45 | - chelitas 46 | - geraldb.github.io 47 | - logos 48 | - sandbox 49 | - talks 50 | - web-proxy-win 51 | - webcomponents 52 | - webpub-reader 53 | - wine.db.tools 54 | 55 | openfootball (41): 56 | - africa-cup 57 | - austria 58 | - club-world-cup 59 | - clubs 60 | - confed-cup 61 | - copa-america 62 | - copa-libertadores 63 | - copa-sudamericana 64 | - deutschland 65 | # ... 66 | ``` 67 | 68 | 69 | Note: If you have more than one account (e.g. an extra robot account or such) 70 | you can add them along e.g. 71 | 72 | 73 | ``` ruby 74 | h = Hubba.reposet( 'geraldb', 'yorobot' ) 75 | pp h 76 | ``` 77 | 78 | 79 | Note: By default all your repos from organizations get auto-included - 80 | use the `orgs: false` option to turn off auto-inclusion. 81 | 82 | Note: By default all (personal) repos, that is, repos in your primary (first) 83 | account that are forks get auto-excluded. 84 | 85 | 86 | 87 | ### Step 2: Update repo statistics (daily / weekly / monthly) 88 | 89 | 90 | #### Basics (commits, stars, forks, topics, etc.) 91 | 92 | Use `update_stats` to 93 | to get the latest commit, star count and more for all your repos 94 | listed in `./repos.yml` via the GitHub API: 95 | 96 | ``` ruby 97 | Hubba.update_stats( './repos.yml' ) 98 | ``` 99 | 100 | Note: By default the datafiles (one per repo) 101 | get stored in the `./data` directory. 102 | 103 | 104 | #### Traffic (page views, clones, referrers, etc.) 105 | 106 | Use `update_traffic` to 107 | to get the latest traffic stats (page views, clones, referrers, etc.) 108 | for all your repos listed in `./repos.yml` via the GitHub API. 109 | 110 | ``` ruby 111 | Hubba.update_traffic( './repos.yml' ) 112 | ``` 113 | 114 | Note: Access to traffic statistics via the GitHub API 115 | requires push access for your GitHub (personal access) token. 116 | 117 | 118 | 119 | 120 | ### Step 3: Generate some statistics / analytics reports 121 | 122 | 123 | See the [hubba-reports gem](https://github.com/rubycoco/git/tree/master/hubba-reports) on how to use pre-built/pre-packaged ready-to-use 124 | github statistics / analytics reports. 125 | 126 | 127 | 128 | That's all for now. 129 | 130 | 131 | 132 | ## Installation 133 | 134 | Use 135 | 136 | gem install hubba 137 | 138 | or add the gem to your Gemfile 139 | 140 | gem 'hubba' 141 | 142 | 143 | ## License 144 | 145 | The `hubba` scripts are dedicated to the public domain. 146 | Use it as you please with no restrictions whatsoever. 147 | -------------------------------------------------------------------------------- /hubba/Rakefile: -------------------------------------------------------------------------------- 1 | require 'hoe' 2 | require './lib/hubba/version.rb' 3 | 4 | Hoe.spec 'hubba' do 5 | 6 | self.version = Hubba::VERSION 7 | 8 | self.summary = 'hubba - (yet) another (lite) GitHub HTTP API client / library' 9 | self.description = summary 10 | 11 | self.urls = { home: 'https://github.com/rubycoco/git' } 12 | 13 | self.author = 'Gerald Bauer' 14 | self.email = 'ruby-talk@ruby-lang.org' 15 | 16 | # switch extension to .markdown for gihub formatting 17 | self.readme_file = 'README.md' 18 | self.history_file = 'CHANGELOG.md' 19 | 20 | self.extra_deps = [ 21 | ['webclient', '>= 0.1.1'] 22 | ] 23 | 24 | self.licenses = ['Public Domain'] 25 | 26 | self.spec_extras = { 27 | required_ruby_version: '>= 2.2.2' 28 | } 29 | 30 | end 31 | -------------------------------------------------------------------------------- /hubba/attic/cache.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | class Cache ## lets you work with GitHub api "offline" using just a local cache of stored json 4 | 5 | def initialize( dir ) 6 | @dir = dir 7 | end 8 | 9 | ## fix/todo: cut of query string e.g. ?per_page=100 why? why not? 10 | def get( request_uri ) 11 | ## check if request_uri exists in local cache 12 | basename = request_uri_to_basename( request_uri ) 13 | path = "#{@dir}/#{basename}.json" 14 | if File.exist?( path ) 15 | text = File.open( path, 'r:utf-8') { |f| f.read } 16 | json = JSON.parse( text ) 17 | json 18 | else 19 | nil ## todo/fix: raise exception - why? why not?? 20 | end 21 | end 22 | 23 | def put( request_uri, obj ) 24 | basename = request_uri_to_basename( request_uri ) 25 | path = "#{@dir}/#{basename}.json" 26 | 27 | if obj.is_a?( Resource ) ## note: for convenience support Resource obj too 28 | data = obj.data 29 | else 30 | data = obj # assume Hash or Array -- todo: add support for String - why? why not?? 31 | end 32 | 33 | File.open( path, 'w:utf-8' ) do |f| 34 | f.write( JSON.pretty_generate( data )) 35 | end 36 | end 37 | 38 | 39 | def request_uri_to_basename( request_uri ) 40 | ## 1) cut off leading / 41 | ## 2) convert / to ~ 42 | ## 3) remove (optional) query string (for now) - why? why not? 43 | ## e.g. /users/#{name}/orgs?per_page=100 or such 44 | ## 45 | ## e.g. 46 | ## '/users/geraldb' => 'users~geraldb', 47 | ## '/users/geraldb/repos' => 'users~geraldb~repos', 48 | ## '/users/geraldb/orgs' => 'users~geraldb~orgs', 49 | ## '/orgs/wikiscript/repos' => 'orgs~wikiscript~repos', 50 | ## '/orgs/planetjekyll/repos' => 'orgs~planetjekyll~repos', 51 | ## '/orgs/vienna-rb/repos' => 'orgs~vienna~rb.repos', 52 | 53 | basename = request_uri[1..-1] 54 | basename = basename.gsub( '/', '~') 55 | basename = basename.sub( /\?.*\z/, '' ) ## note: must escape ? (use \z for $) 56 | basename 57 | end 58 | 59 | end ## class Cache 60 | 61 | end ## module Hubba 62 | -------------------------------------------------------------------------------- /hubba/attic/cache/users~geraldb~orgs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "login": "vienna-rb", 4 | "id": 3541331, 5 | "url": "https://api.github.com/orgs/vienna-rb", 6 | "repos_url": "https://api.github.com/orgs/vienna-rb/repos", 7 | "events_url": "https://api.github.com/orgs/vienna-rb/events", 8 | "members_url": "https://api.github.com/orgs/vienna-rb/members{/member}", 9 | "public_members_url": "https://api.github.com/orgs/vienna-rb/public_members{/member}", 10 | "avatar_url": "https://avatars.githubusercontent.com/u/3541331?v=3", 11 | "description": null 12 | }, 13 | { 14 | "login": "openbeer", 15 | "id": 4464191, 16 | "url": "https://api.github.com/orgs/openbeer", 17 | "repos_url": "https://api.github.com/orgs/openbeer/repos", 18 | "events_url": "https://api.github.com/orgs/openbeer/events", 19 | "members_url": "https://api.github.com/orgs/openbeer/members{/member}", 20 | "public_members_url": "https://api.github.com/orgs/openbeer/public_members{/member}", 21 | "avatar_url": "https://avatars.githubusercontent.com/u/4464191?v=3", 22 | "description": "Open Public Domain Beer, Brewery n Brewpub Data - Incl. The Free World Beer Book" 23 | }, 24 | { 25 | "login": "slideshow-s9", 26 | "id": 4464373, 27 | "url": "https://api.github.com/orgs/slideshow-s9", 28 | "repos_url": "https://api.github.com/orgs/slideshow-s9/repos", 29 | "events_url": "https://api.github.com/orgs/slideshow-s9/events", 30 | "members_url": "https://api.github.com/orgs/slideshow-s9/members{/member}", 31 | "public_members_url": "https://api.github.com/orgs/slideshow-s9/public_members{/member}", 32 | "avatar_url": "https://avatars.githubusercontent.com/u/4464373?v=3", 33 | "description": "Free Web Alternative to PowerPoint and Keynote Using Easy-to-Write and Easy-to-Read Plain Text Wiki-Style Markup" 34 | }, 35 | { 36 | "login": "openfootball", 37 | "id": 4477026, 38 | "url": "https://api.github.com/orgs/openfootball", 39 | "repos_url": "https://api.github.com/orgs/openfootball/repos", 40 | "events_url": "https://api.github.com/orgs/openfootball/events", 41 | "members_url": "https://api.github.com/orgs/openfootball/members{/member}", 42 | "public_members_url": "https://api.github.com/orgs/openfootball/public_members{/member}", 43 | "avatar_url": "https://avatars.githubusercontent.com/u/4477026?v=3", 44 | "description": "Open Public Domain Football Data - Incl. The Free World Football Almanac" 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /hubba/attic/github.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | def initialize( cache_dir: './cache' ) 4 | @cache = Cache.new( cache_dir ) 5 | 6 | ## ... 7 | 8 | @offline = false 9 | end 10 | 11 | def offline!() @offline = true; end ## switch to offline - todo: find a "better" way - why? why not? 12 | def online!() @offline = false; end 13 | def offline?() @offline == true; end 14 | def online?() @offline == false; end 15 | 16 | 17 | def get( request_uri ) 18 | if offline? 19 | @cache.get( request_uri ) 20 | else 21 | @client.get( request_uri ) 22 | end 23 | end 24 | 25 | end # class Github 26 | 27 | end # module Hubba 28 | -------------------------------------------------------------------------------- /hubba/attic/test_cache.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib -I ./test test/test_cache.rb 4 | 5 | 6 | require 'helper' 7 | 8 | 9 | class TestCache < MiniTest::Test 10 | 11 | def setup 12 | @cache = Hubba::Cache.new( "#{Hubba.root}/test/cache" ) 13 | end 14 | 15 | def test_basename 16 | mappings = [['/users/geraldb', 'users~geraldb'], 17 | ['/users/geraldb/repos', 'users~geraldb~repos'], 18 | ['/users/geraldb/repos?per_page=100', 'users~geraldb~repos'], 19 | ['/users/geraldb/orgs', 'users~geraldb~orgs'], 20 | ['/users/geraldb/orgs?per_page=100', 'users~geraldb~orgs'], 21 | ['/orgs/wikiscript/repos', 'orgs~wikiscript~repos'], 22 | ['/orgs/planetjekyll/repos', 'orgs~planetjekyll~repos'], 23 | ['/orgs/vienna-rb/repos', 'orgs~vienna-rb~repos']] 24 | 25 | mappings.each do |mapping| 26 | assert_equal mapping[1], @cache.request_uri_to_basename( mapping[0] ) 27 | end 28 | end # method test_basename 29 | 30 | def test_cache 31 | orgs = @cache.get( '/users/geraldb/orgs' ) 32 | assert_equal 4, orgs.size 33 | 34 | repos = @cache.get( '/users/geraldb/repos' ) 35 | assert_equal 3, repos.size 36 | end # method test_cache 37 | 38 | def test_cache_miss 39 | assert_nil @cache.get( '/test/hello' ) 40 | assert_nil @cache.get( '/test/hola' ) 41 | end # method test_cache_miss 42 | 43 | end # class TestCache 44 | -------------------------------------------------------------------------------- /hubba/lib/hubba.rb: -------------------------------------------------------------------------------- 1 | # 3rd party (our own) 2 | require 'webclient' 3 | 4 | 5 | ############### 6 | ## helpers 7 | 8 | def save_json( path, data ) ## data - hash or array 9 | File.open( path, 'w:utf-8' ) do |f| 10 | f.write( JSON.pretty_generate( data )) 11 | end 12 | end 13 | 14 | def save_yaml( path, data ) 15 | File.open( path, 'w:utf-8' ) do |f| 16 | f.write( data.to_yaml ) 17 | end 18 | end 19 | 20 | 21 | 22 | # our own code 23 | require 'hubba/version' # note: let version always go first 24 | 25 | module Hubba 26 | class HttpError < StandardError 27 | end 28 | end 29 | 30 | require 'hubba/config' 31 | require 'hubba/github' 32 | require 'hubba/reposet' 33 | 34 | ### update stats (github data) machinery 35 | require 'hubba/stats' 36 | require 'hubba/update' 37 | require 'hubba/update_traffic' 38 | 39 | 40 | 41 | 42 | ############ 43 | # add convenience alias for alternate spelling - why? why not? 44 | module Hubba 45 | GitHub = Github 46 | end 47 | 48 | 49 | # say hello 50 | puts Hubba.banner 51 | 52 | 53 | -------------------------------------------------------------------------------- /hubba/lib/hubba/config.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | class Configuration 4 | def data_dir() @data_dir || './data'; end 5 | def data_dir=( value ) @data_dir = value; end 6 | 7 | ### todo/check: rename to/use tmp_dir - why? why not? 8 | def cache_dir() @cache_dir || './cache'; end 9 | def cache_dir=( value ) @cache_dir = value; end 10 | 11 | 12 | # try default setup via ENV variables 13 | def token() @token || ENV[ 'HUBBA_TOKEN' ]; end 14 | def token=( value ) @token = value; end 15 | 16 | # todo/check: use HUBBA_LOGIN - why? why not? 17 | def user() @user || ENV[ 'HUBBA_USER' ]; end 18 | def password() @password || ENV[ 'HUBBA_PASSWORD' ]; end 19 | def user=( value ) @user = value; end 20 | def password=( value ) @password = value; end 21 | 22 | end # class Configuration 23 | 24 | 25 | ## lets you use 26 | ## Hubba.configure do |config| 27 | ## config.token = 'secret' 28 | ## -or- 29 | ## config.user = 'testdada' 30 | ## config.password = 'secret' 31 | ## end 32 | ## 33 | ## move configure block to GitHub class - why? why not? 34 | ## e.g. GitHub.configure do |config| 35 | ## ... 36 | ## end 37 | 38 | 39 | def self.configuration 40 | @configuration ||= Configuration.new 41 | end 42 | class << self 43 | alias_method :config, :configuration 44 | end 45 | 46 | 47 | def self.configure 48 | yield( configuration ) 49 | end 50 | 51 | end # module Hubba 52 | -------------------------------------------------------------------------------- /hubba/lib/hubba/reposet.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | 4 | ## orgs - include repos form org(anizations) too 5 | ## cache - save json response to cache_dir - change to/use debug/tmp_dir? - why? why not? 6 | def self.reposet( *users, orgs: true, 7 | cache: false ) 8 | # users = [users] if users.is_a?( String ) ### wrap in array if single user 9 | 10 | gh = Github.new 11 | 12 | forks = [] 13 | 14 | h = {} 15 | users.each do |user| 16 | res = gh.user_repos( user ) 17 | save_json( "#{config.cache_dir}/users~#{user}~repos.json", res.data ) if cache 18 | 19 | repos = [] 20 | #### 21 | # check for forked repos (auto-exclude personal by default) 22 | # note: forked repos in orgs get NOT auto-excluded!!! 23 | res.data.each do |repo| 24 | fork = repo['fork'] 25 | if fork 26 | print "FORK " 27 | forks << "#{repo['full_name']} (AUTO-EXCLUDED)" 28 | else 29 | print " " 30 | repos << repo['name'] 31 | end 32 | print repo['full_name'] 33 | print "\n" 34 | end 35 | 36 | 37 | h[ "#{user} (#{repos.size})" ] = repos.sort 38 | end 39 | 40 | 41 | ## all repos from orgs 42 | ## note: for now only use first (primary user) - why? why not? 43 | if orgs 44 | user = users[0] 45 | res = gh.user_orgs( user ) 46 | save_json( "#{config.cache_dir}/users~#{user}~orgs.json", res.data ) if cache 47 | 48 | 49 | logins = res.logins.each do |login| 50 | ## next if ['xxx'].include?( login ) ## add orgs here to skip 51 | 52 | res = gh.org_repos( login ) 53 | save_json( "#{config.cache_dir}/orgs~#{login}~repos.json", res.data ) if cache 54 | 55 | repos = [] 56 | res.data.each do |repo| 57 | fork = repo['fork'] 58 | if fork 59 | print "FORK " 60 | forks << repo['full_name'] 61 | repos << repo['name'] 62 | else 63 | print " " 64 | repos << repo['name'] 65 | end 66 | print repo['full_name'] 67 | print "\n" 68 | end 69 | 70 | h[ "#{login} (#{repos.size})" ] = repos.sort 71 | end 72 | end 73 | 74 | if forks.size > 0 75 | puts 76 | puts "#{forks.size} fork(s):" 77 | puts forks 78 | end 79 | 80 | h 81 | end ## method reposet 82 | 83 | end # module Hubba 84 | -------------------------------------------------------------------------------- /hubba/lib/hubba/update.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | def self.update_stats( hash_or_path='./repos.yml' ) ## move to reposet e.g. Reposet#update_status!!!! 4 | h = if hash_or_path.is_a?( String ) ## assume it is a file path!!! 5 | path = hash_or_path 6 | YAML.load_file( path ) 7 | else 8 | hash_or_path # assume its a hash / reposet already!!! 9 | end 10 | 11 | gh = Github.new 12 | 13 | h.each do |org_with_counter,names| 14 | 15 | ## remove optional number from key e.g. 16 | ## mrhydescripts (3) => mrhydescripts 17 | ## footballjs (4) => footballjs 18 | ## etc. 19 | org = org_with_counter.sub( /\([0-9]+\)/, '' ).strip 20 | 21 | ## puts " -- #{key_with_counter} [#{key}] --" 22 | 23 | names.each do |name| 24 | full_name = "#{org}/#{name}" 25 | 26 | ## puts " fetching stats #{count+1}/#{repo_count} - >#{full_name}<..." 27 | stats = Stats.new( full_name ) 28 | stats.read 29 | 30 | puts "update >#{full_name}< [1/4] - fetching repo..." 31 | repo = gh.repo( full_name ) 32 | puts "update >#{full_name}< [2/4] - fetching repo commits ..." 33 | commits = gh.repo_commits( full_name ) 34 | puts "update >#{full_name}< [3/4] - fetching repo topics ..." 35 | topics = gh.repo_topics( full_name ) 36 | puts "update >#{full_name}< [4/4] - fetching repo languages ..." 37 | languages = gh.repo_languages( full_name ) 38 | 39 | stats.update( repo, 40 | commits: commits, 41 | topics: topics, 42 | languages: languages ) 43 | stats.write 44 | end 45 | end 46 | end 47 | end # module Hubba -------------------------------------------------------------------------------- /hubba/lib/hubba/update_traffic.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | 3 | 4 | ### 5 | ## note: keep update traffic separate from update (basic) stats 6 | ## traffic stats require (personal access) token with push access!! 7 | 8 | def self.update_traffic( hash_or_path='./repos.yml' ) ## move to reposet e.g. Reposet#update_status!!!! 9 | h = if hash_or_path.is_a?( String ) ## assume it is a file path!!! 10 | path = hash_or_path 11 | YAML.load_file( path ) 12 | else 13 | hash_or_path # assume its a hash / reposet already!!! 14 | end 15 | 16 | gh = Github.new 17 | 18 | h.each do |org_with_counter,names| 19 | 20 | ## remove optional number from key e.g. 21 | ## mrhydescripts (3) => mrhydescripts 22 | ## footballjs (4) => footballjs 23 | ## etc. 24 | org = org_with_counter.sub( /\([0-9]+\)/, '' ).strip 25 | 26 | ## puts " -- #{key_with_counter} [#{key}] --" 27 | 28 | names.each do |name| 29 | full_name = "#{org}/#{name}" 30 | 31 | ## puts " fetching stats #{count+1}/#{repo_count} - >#{full_name}<..." 32 | stats = Stats.new( full_name ) 33 | stats.read 34 | 35 | puts "update >#{full_name}< [1/4] - fetching repo traffic clones..." 36 | clones = gh.repo_traffic_clones( full_name ) 37 | puts "update >#{full_name}< [2/4] - fetching repo traffic views..." 38 | views = gh.repo_traffic_views( full_name ) 39 | puts "update >#{full_name}< [3/4] - fetching repo traffic popular paths..." 40 | paths = gh.repo_traffic_popular_paths( full_name ) 41 | puts "update >#{full_name}< [4/4] - fetching repo traffic popular referrers..." 42 | referrers = gh.repo_traffic_popular_referrers( full_name ) 43 | 44 | stats.update_traffic( clones: clones, 45 | views: views, 46 | paths: paths, 47 | referrers: referrers ) 48 | stats.write 49 | end 50 | end 51 | end 52 | end # module Hubba -------------------------------------------------------------------------------- /hubba/lib/hubba/version.rb: -------------------------------------------------------------------------------- 1 | module Hubba 2 | MAJOR = 1 ## todo: namespace inside version or something - why? why not?? 3 | MINOR = 0 4 | PATCH = 3 5 | VERSION = [MAJOR,MINOR,PATCH].join('.') 6 | 7 | def self.version 8 | VERSION 9 | end 10 | 11 | def self.banner 12 | "hubba/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" 13 | end 14 | 15 | def self.root 16 | File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) ) 17 | end 18 | end # module Hubba 19 | -------------------------------------------------------------------------------- /hubba/sandbox/test.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift( "./lib" ) 2 | require 'hubba' 3 | 4 | ##################### 5 | ## note: 6 | ## set HUBBA_TOKEN on environment first!!!! 7 | #################### 8 | 9 | gh = Hubba::Github.new 10 | 11 | pp gh.user( 'geraldb') 12 | 13 | repos = gh.user_repos( 'geraldb' ) 14 | pp repos 15 | puts 16 | puts "#{repos.names.size} repo(s) - geraldb:" 17 | puts repos.names 18 | 19 | 20 | orgs = gh.user_orgs( 'geraldb' ) 21 | pp orgs 22 | puts 23 | puts "#{orgs.logins.size} org(s) -geraldb:" 24 | puts orgs.logins 25 | 26 | 27 | repos = gh.user_repos( 'yorobot' ) 28 | ## pp repos 29 | puts 30 | puts "#{repos.names.size} repo(s) - yorobot:" 31 | puts repos.names 32 | 33 | 34 | repos = gh.org_repos( 'rubycoco' ) 35 | ## pp repos 36 | puts 37 | puts "#{repos.names.size} repo(s) - rubycoco:" 38 | puts repos.names 39 | 40 | repos = gh.org_repos( 'openfootball' ) 41 | ## pp repos 42 | puts 43 | puts "#{repos.names.size} repo(s) - openfootball:" 44 | puts repos.names 45 | -------------------------------------------------------------------------------- /hubba/sandbox/test_orgs.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift( "./lib" ) 2 | require 'hubba' 3 | 4 | ##################### 5 | ## note: 6 | ## set HUBBA_TOKEN on environment first!!!! 7 | #################### 8 | 9 | gh = Hubba::Github.new 10 | 11 | =begin 12 | orgs = gh.user_orgs( 'yorobot' ) 13 | pp orgs 14 | puts 15 | puts "#{orgs.logins.size} org(s) - yorobot:" 16 | puts orgs.logins 17 | puts 18 | 19 | orgs = gh.user_orgs( 'geraldb' ) 20 | pp orgs 21 | puts 22 | puts "#{orgs.logins.size} org(s) - geraldb:" 23 | puts orgs.logins 24 | puts 25 | =end 26 | 27 | # org = gh.org( 'openbeer' ) 28 | org = gh.org( 'openfootball' ) 29 | pp org 30 | 31 | 32 | -------------------------------------------------------------------------------- /hubba/sandbox/test_stats.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift( "./lib" ) 2 | require 'hubba' 3 | 4 | ##################### 5 | ## note: 6 | ## set HUBBA_TOKEN on environment first!!!! 7 | #################### 8 | 9 | gh = Hubba::Github.new 10 | 11 | 12 | stats = Hubba::Stats.new( 'openfootball/deutschland' ) 13 | pp stats 14 | puts 15 | 16 | ## gh.update( stats ) 17 | 18 | 19 | pp stats 20 | 21 | Hubba.config.data_dir = './tmp' 22 | stats.write 23 | 24 | 25 | -------------------------------------------------------------------------------- /hubba/test/helper.rb: -------------------------------------------------------------------------------- 1 | # minitest setup 2 | require 'minitest/autorun' 3 | 4 | 5 | ## our own code 6 | require 'hubba' 7 | 8 | -------------------------------------------------------------------------------- /hubba/test/test_config.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib -I ./test test/test_config.rb 4 | 5 | 6 | require 'helper' 7 | 8 | 9 | class TestConfig < MiniTest::Test 10 | 11 | def test_config 12 | Hubba.configure do |config| 13 | config.user = 'user1' 14 | config.password = 'password1' 15 | # -or- 16 | config.token = 'token1' 17 | end 18 | 19 | assert_equal 'user1', Hubba.configuration.user 20 | assert_equal 'password1', Hubba.configuration.password 21 | assert_equal 'token1', Hubba.configuration.token 22 | 23 | assert_equal 'user1', Hubba.config.user 24 | assert_equal 'password1', Hubba.config.password 25 | assert_equal 'token1', Hubba.config.token 26 | 27 | assert_equal './data', Hubba.configuration.data_dir 28 | assert_equal './data', Hubba.config.data_dir 29 | end 30 | 31 | end # class TestConfig 32 | -------------------------------------------------------------------------------- /monofile/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /lib/bundler/man/ 26 | 27 | # for a library or gem, you might want to ignore these files since the code is 28 | # intended to run in multiple environments; otherwise, check them in: 29 | # Gemfile.lock 30 | # .ruby-version 31 | # .ruby-gemset 32 | 33 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 34 | .rvmrc 35 | 36 | 37 | # more debugging support 38 | errors.log -------------------------------------------------------------------------------- /monofile/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.1 / 2020-10-29 2 | 3 | * Everything is new. First release. 4 | -------------------------------------------------------------------------------- /monofile/Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | Manifest.txt 3 | README.md 4 | Rakefile 5 | bin/monofile 6 | lib/monofile.rb 7 | lib/monofile/monofile.rb 8 | lib/monofile/mononame.rb 9 | lib/monofile/tool.rb 10 | lib/monofile/version.rb 11 | test/helper.rb 12 | test/test_names.rb 13 | -------------------------------------------------------------------------------- /monofile/README.md: -------------------------------------------------------------------------------- 1 | # monofile - read in / parse monorepo / mono source tree definitions - a list of git (and github) projects, and more 2 | 3 | * home :: [github.com/rubycocos/git](https://github.com/rubycocos/git) 4 | * bugs :: [github.com/rubycocos/git/issues](https://github.com/rubycocos/git/issues) 5 | * gem :: [rubygems.org/gems/monofile](https://rubygems.org/gems/monofile) 6 | * rdoc :: [rubydoc.info/gems/monofile](http://rubydoc.info/gems/monofile) 7 | 8 | 9 | 10 | ## Usage 11 | 12 | 13 | Use `Monofile.read` to read in / parse monorepo / mono source tree definitions - supporting a ruby or a yaml format. 14 | 15 | 16 | Example - `Monofile`: 17 | ``` ruby 18 | project "@openfootball/england" 19 | project "@openfootball/world-cup" 20 | project "@geraldb/austria" 21 | project "@geraldb/geraldb.github.io" 22 | 23 | project "geraldb", "catalog" 24 | project "openfootball", "europe" 25 | project "openfootball", "south-america" 26 | ``` 27 | 28 | or 29 | 30 | Example - `monofile.yml`: 31 | 32 | ``` yaml 33 | geraldb: 34 | - austria 35 | - catalog 36 | - geraldb.github.io 37 | 38 | openfootball: 39 | - england 40 | - europe 41 | - south-america 42 | - world-cup 43 | ``` 44 | 45 | 46 | To read use. 47 | 48 | ``` ruby 49 | monofile = Monofile.read( "./Monofile" ) 50 | # -or- 51 | monofile = Monofile.read( "./monofile.yml" ) 52 | pp monofile.to_a 53 | #=> ["@openfootball/england", 54 | # "@openfootball/world-cup", 55 | # "@geraldb/austria", 56 | # "@geraldb/geraldb.github.io", 57 | # "@geraldb/catalog", 58 | # "@openfootball/europe"] 59 | # "@openfootball/south-america"] 60 | 61 | pp monofile.to_h 62 | #=> {"openfootball"=>["england", "world-cup", "europe", "south-america"], 63 | # "geraldb" =>["austria", "geraldb.github.io", "catalog"]} 64 | 65 | monofile.each do |proj| 66 | puts " #{proj}" 67 | end 68 | #=> @openfootball/england 69 | # @openfootball/world-cup 70 | # @geraldb/austria 71 | # @geraldb/geraldb.github.io 72 | # @geraldb/catalog 73 | # @openfootball/europe 74 | # @openfootball/south-america 75 | 76 | monofile.size 77 | #=> 7 78 | ``` 79 | 80 | and so on. That's it for now. 81 | 82 | 83 | 84 | ### Troubleshooting / Debugging 85 | 86 | Use the `monofile` command line tool to test reading in of 87 | monorepo / mono source tree definitions. 88 | Example: 89 | 90 | ``` shell 91 | # option 1) try to find default name (e.g. Monofile, Monofile.rb, etc.) 92 | $ monofile 93 | 94 | # option 2) pass in monofiles 95 | $ monofile ./Monofile 96 | $ monofile ./monfile.yml 97 | # ... 98 | ``` 99 | 100 | Printing the normalized / canonical names of the repo sources. Example. 101 | 102 | ``` 103 | @openfootball/england 104 | @openfootball/world-cup 105 | @geraldb/austria 106 | @geraldb/geraldb.github.io 107 | @geraldb/catalog 108 | @openfootball/europe 109 | @openfootball/south-america 110 | ``` 111 | 112 | 113 | 114 | 115 | ## Real-World Usage 116 | 117 | See the [`monos`](https://github.com/rubycocos/git/tree/master/monos) package that incl. the `mono` (or short `mo`) 118 | command line tool lets you run 119 | git commands on multiple repo(sitories) with a single command. 120 | 121 | 122 | ## Installation 123 | 124 | Use 125 | 126 | gem install monofile 127 | 128 | or add to your Gemfile 129 | 130 | gem 'monofile' 131 | 132 | 133 | 134 | ## License 135 | 136 | The `monofile` scripts are dedicated to the public domain. 137 | Use it as you please with no restrictions whatsoever. 138 | 139 | -------------------------------------------------------------------------------- /monofile/Rakefile: -------------------------------------------------------------------------------- 1 | require 'hoe' 2 | require './lib/monofile/version.rb' 3 | 4 | 5 | Hoe.spec 'monofile' do 6 | 7 | self.version = Mono::Module::Monofile::VERSION 8 | 9 | self.summary = "monofile - read in / parse monorepo / mono source tree definitions - a list of git (and github) projects, and more" 10 | self.description = summary 11 | 12 | self.urls = { home: 'https://github.com/rubycocos/git' } 13 | 14 | self.author = 'Gerald Bauer' 15 | self.email = 'opensport@googlegroups.com' 16 | 17 | # switch extension to .markdown for gihub formatting 18 | self.readme_file = 'README.md' 19 | self.history_file = 'CHANGELOG.md' 20 | 21 | self.licenses = ['Public Domain'] 22 | 23 | self.extra_deps = [] 24 | 25 | self.spec_extras = { 26 | required_ruby_version: '>= 2.2.2' 27 | } 28 | end 29 | -------------------------------------------------------------------------------- /monofile/bin/monofile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ################### 4 | # DEV TIPS: 5 | # 6 | # For local testing run like: 7 | # 8 | # ruby -Ilib bin/monofile 9 | # 10 | # Set the executable bit in Linux. Example: 11 | # 12 | # % chmod a+x bin/monofile 13 | # 14 | 15 | require 'monofile' 16 | 17 | Monofile::Tool.main -------------------------------------------------------------------------------- /monofile/lib/monofile.rb: -------------------------------------------------------------------------------- 1 | ## 2 | ## "prelude / prolog " add some common used stdlibs 3 | ## add more - why? why not? 4 | require 'pp' 5 | require 'time' 6 | require 'date' 7 | require 'json' 8 | require 'yaml' 9 | require 'fileutils' 10 | 11 | require 'uri' 12 | require 'net/http' 13 | require 'net/https' 14 | 15 | 16 | require 'optparse' ## used by monofile (built-in test/debug) command line tool 17 | 18 | 19 | 20 | ##################### 21 | # our own code 22 | require 'monofile/version' # note: let version always go first 23 | require 'monofile/mononame' 24 | require 'monofile/monofile' 25 | require 'monofile/tool' 26 | 27 | 28 | 29 | module Mono 30 | 31 | def self.root ## root of single (monorepo) source tree 32 | @@root ||= begin 33 | ## todo/fix: 34 | ## check if windows - otherwise use /sites 35 | ## check if root directory exists? 36 | if ENV['MOPATH'] 37 | ## use expand path to make (assure) absolute path - why? why not? 38 | File.expand_path( ENV['MOPATH'] ) 39 | elsif Dir.exist?( 'C:/Sites' ) 40 | 'C:/Sites' 41 | else 42 | '/sites' 43 | end 44 | end 45 | end 46 | 47 | def self.root=( path ) 48 | ## use expand path to make (assure) absolute path - why? why not? 49 | @@root = File.expand_path( path ) 50 | end 51 | end ## module Mono 52 | 53 | 54 | 55 | 56 | ### 57 | ## add some convenience alias for alternate spelling (CamelCase) 58 | MonoName = Mononame 59 | MonoPath = Monopath 60 | MonoFile = Monofile 61 | 62 | 63 | puts Mono::Module::Monofile.banner ## say hello 64 | 65 | -------------------------------------------------------------------------------- /monofile/lib/monofile/tool.rb: -------------------------------------------------------------------------------- 1 | class Monofile 2 | class Tool 3 | def self.main( args=ARGV ) 4 | 5 | options = {} 6 | OptionParser.new do |parser| 7 | ## note: 8 | ## you can add many/multiple modules 9 | ## e.g. -r gitti -r mono etc. 10 | parser.on( '-r NAME', '--require NAME') do |name| 11 | options[:requires] ||= [] 12 | options[:requires] << name 13 | end 14 | ## todo/fix: 15 | ## add --verbose 16 | ## add -d/--debug 17 | end.parse!( args ) 18 | 19 | 20 | if args.size == 0 ## auto-add default arg (monofile) 21 | monofile_path = Monofile.find 22 | if monofile_path.nil? 23 | puts "!! ERROR: no mono configuration file found; looking for #{Monofile::NAMES.join(', ')} in (#{Dir.getwd})" 24 | exit 1 25 | end 26 | args << monofile_path 27 | end 28 | 29 | 30 | ## add check for auto-require (e.g. ./config.rb) 31 | if options[:requires] ## use custom (auto-)requires 32 | options[:requires].each do |path| 33 | puts "[monofile] auto-require >#{path}<..." 34 | require( path ) 35 | end 36 | else ## use/try defaults 37 | config_path = "./config.rb" 38 | if File.exist?( config_path ) 39 | puts "[monofile] auto-require (default) >#{config_path}<..." 40 | require( config_path ) 41 | end 42 | end 43 | 44 | 45 | args.each do |path| 46 | puts "[monofile] reading >#{path}<..." 47 | monofile=Monofile.read( path ) 48 | pp monofile 49 | 50 | ## print one project per line 51 | puts "---" 52 | monofile.each do |proj| 53 | puts proj.to_s 54 | end 55 | end 56 | end # method self.main 57 | end # (nested) class Tool 58 | end # class Monofile 59 | 60 | -------------------------------------------------------------------------------- /monofile/lib/monofile/version.rb: -------------------------------------------------------------------------------- 1 | 2 | module Mono 3 | module Module 4 | module Monofile 5 | 6 | MAJOR = 0 ## todo: namespace inside version or something - why? why not?? 7 | MINOR = 2 8 | PATCH = 4 9 | VERSION = [MAJOR,MINOR,PATCH].join('.') 10 | 11 | def self.version 12 | VERSION 13 | end 14 | 15 | def self.banner 16 | "monofile/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" 17 | end 18 | 19 | ## note: move root to its own namespace to avoid 20 | ## conflict with Mono.root!!!! 21 | def self.root 22 | File.expand_path( File.dirname(File.dirname(__FILE__) )) 23 | end 24 | 25 | end # module Monofile 26 | end # module Module 27 | end # module Mono 28 | 29 | -------------------------------------------------------------------------------- /monofile/sandbox/monofile.yml: -------------------------------------------------------------------------------- 1 | geraldb: 2 | - austria 3 | - catalog 4 | - geraldb.github.io 5 | 6 | openfootball: 7 | - england 8 | - europe 9 | - south-america 10 | - world-cup -------------------------------------------------------------------------------- /monofile/sandbox/test_load.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift( "./lib" ) 2 | require 'monofile' 3 | 4 | 5 | monofile = Monofile.load( < Monofile::Builder 16 | ## try "batch" adding projects 17 | projects( {}, 18 | { 'footballcsv': 19 | ['england', 'world'] } 20 | ) 21 | 22 | 23 | puts "hello from monofile" 24 | TXT 25 | 26 | 27 | 28 | puts "---" 29 | pp monofile 30 | 31 | 32 | 33 | puts 34 | puts "to_a:" 35 | pp monofile.to_a 36 | 37 | puts "to_h:" 38 | pp monofile.to_h 39 | 40 | puts 41 | puts "each (org,names):" 42 | monofile.each do |org,names| 43 | puts " #{org} (#{names.length}): #{names.join(', ')}" 44 | end 45 | 46 | puts 47 | puts "each (project):" 48 | monofile.each do |proj| 49 | puts " #{proj}" 50 | end 51 | 52 | puts "size: #{monofile.size}" 53 | 54 | -------------------------------------------------------------------------------- /monofile/sandbox/test_read.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift( "./lib" ) 2 | require 'monofile' 3 | 4 | 5 | monofile = Monofile.read( "./sandbox/monofile.yml" ) 6 | pp monofile 7 | puts 8 | puts "to_a:" 9 | pp monofile.to_a 10 | 11 | puts "to_h:" 12 | pp monofile.to_h 13 | 14 | puts 15 | puts "each (org,names):" 16 | monofile.each do |org,names| 17 | puts " #{org} (#{names.length}): #{names.join(', ')}" 18 | end 19 | 20 | puts 21 | puts "each (project):" 22 | monofile.each do |proj| 23 | puts " #{proj}" 24 | end 25 | 26 | puts "size: #{monofile.size}" 27 | 28 | -------------------------------------------------------------------------------- /monofile/test/helper.rb: -------------------------------------------------------------------------------- 1 | # minitest setup 2 | require 'minitest/autorun' 3 | 4 | 5 | ## our own code 6 | require 'monofile' 7 | 8 | -------------------------------------------------------------------------------- /monofile/test/test_names.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib -I ./test test/test_names.rb 4 | 5 | 6 | require 'helper' 7 | 8 | 9 | class TestNames < MiniTest::Test 10 | 11 | def test_parse 12 | %w[ 13 | @openfootball/england 14 | england@openfootball 15 | ].each do |line| 16 | mono = Mononame.parse( line ) 17 | 18 | assert_equal '@openfootball/england', mono.to_s 19 | assert_equal 'openfootball/england', mono.to_path 20 | 21 | assert_equal 'openfootball', mono.org 22 | assert_equal 'england', mono.name 23 | end 24 | 25 | 26 | %w[ 27 | @openfootball/england/2020-21 28 | 2020-21@openfootball/england 29 | england/2020-21@openfootball 30 | ].each do |line| 31 | mono = Monopath.parse( line ) 32 | 33 | assert_equal '@openfootball/england/2020-21', mono.to_s 34 | assert_equal 'openfootball/england/2020-21', mono.to_path 35 | 36 | assert_equal 'openfootball', mono.org 37 | assert_equal 'england', mono.name 38 | assert_equal '2020-21', mono.path 39 | end 40 | 41 | %w[ 42 | @openfootball/england/2020-21/premierleague.txt 43 | 2020-21/premierleague.txt@openfootball/england 44 | england/2020-21/premierleague.txt@openfootball 45 | ].each do |line| 46 | mono = Monopath.parse( line ) 47 | 48 | assert_equal '@openfootball/england/2020-21/premierleague.txt', mono.to_s 49 | assert_equal 'openfootball/england/2020-21/premierleague.txt', mono.to_path 50 | 51 | assert_equal 'openfootball', mono.org 52 | assert_equal 'england', mono.name 53 | assert_equal '2020-21/premierleague.txt', mono.path 54 | end 55 | end # method test_parse 56 | 57 | 58 | 59 | def test_init 60 | mono = Mononame.new( 'openfootball','england' ) 61 | 62 | assert_equal '@openfootball/england', mono.to_s 63 | assert_equal 'openfootball/england', mono.to_path 64 | 65 | assert_equal 'openfootball', mono.org 66 | assert_equal 'england', mono.name 67 | 68 | 69 | mono = Monopath.new( 'openfootball', 'england', '2020-21' ) 70 | 71 | assert_equal '@openfootball/england/2020-21', mono.to_s 72 | assert_equal 'openfootball/england/2020-21', mono.to_path 73 | 74 | assert_equal 'openfootball', mono.org 75 | assert_equal 'england', mono.name 76 | assert_equal '2020-21', mono.path 77 | 78 | 79 | ## !!!!todo/check/fix!!!!!: 80 | ## - support '2020-21', 'premierleague.txt' too (or only) - why? why not? 81 | ## 82 | ## todo/check/fix: 83 | ## find a better name for path/path? component / part - why? why not? 84 | ## to_path and path/path? to confusing!!! 85 | mono = Monopath.new( 'openfootball', 'england', '2020-21/premierleague.txt' ) 86 | 87 | assert_equal '@openfootball/england/2020-21/premierleague.txt', mono.to_s 88 | assert_equal 'openfootball/england/2020-21/premierleague.txt', mono.to_path 89 | 90 | assert_equal 'openfootball', mono.org 91 | assert_equal 'england', mono.name 92 | assert_equal '2020-21/premierleague.txt', mono.path 93 | end # method test_init 94 | 95 | 96 | 97 | end # class TestNames 98 | -------------------------------------------------------------------------------- /monos/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /lib/bundler/man/ 26 | 27 | # for a library or gem, you might want to ignore these files since the code is 28 | # intended to run in multiple environments; otherwise, check them in: 29 | # Gemfile.lock 30 | # .ruby-version 31 | # .ruby-gemset 32 | 33 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 34 | .rvmrc 35 | 36 | 37 | # more debugging support 38 | errors.log 39 | -------------------------------------------------------------------------------- /monos/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.1 / 2020-08-30 2 | 3 | * Everything is new. First release. 4 | -------------------------------------------------------------------------------- /monos/Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | Manifest.txt 3 | README.md 4 | Rakefile 5 | bin/mo 6 | bin/mono 7 | lib/mono.rb 8 | lib/mono/base.rb 9 | lib/mono/commands/backup.rb 10 | lib/mono/commands/env.rb 11 | lib/mono/commands/fetch.rb 12 | lib/mono/commands/run.rb 13 | lib/mono/commands/status.rb 14 | lib/mono/commands/sync.rb 15 | lib/mono/experimental.rb 16 | lib/mono/tool.rb 17 | lib/mono/version.rb 18 | lib/monos.rb 19 | test/helper.rb 20 | test/test_base.rb 21 | test/test_path.rb 22 | -------------------------------------------------------------------------------- /monos/NOTES.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | ## Monorepos 4 | 5 | ### Articles 6 | 7 | - 8 | - 9 | - 10 | - 11 | 12 | 13 | More 14 | 15 | - 16 | - 17 | - 18 | - 19 | - 20 | - 21 | 22 | 23 | 24 | 25 | ### Tooling 26 | 27 | #### Repo (Python) 28 | _a tool built on top of Git. Repo helps manage many Git repositories, does the uploads to revision control systems, and automates parts of the development workflow. Repo is not meant to replace Git, only to make it easier to work with Git. The repo command is an executable Python script that you can put anywhere in your path_ 29 | 30 | - 31 | - - Manifest Format 32 | - 33 | 34 | #### Lerna (Javascript/Node) 35 | - - a tool for managing JavaScript projects with multiple packages 36 | 37 | #### Yarn Workspaces (Javascript/Node) 38 | - 39 | 40 | #### Rush (Javascript/Node) 41 | 42 | _a scalable monorepo manager for the web_ 43 | 44 | - 45 | - 46 | 47 | 48 | 49 | 50 | More 51 | 52 | - 53 | - - build orchestration tool for monorepo 54 | 55 | 56 | #### Ruby 57 | 58 | - 59 | - 60 | 61 | 62 | --- 63 | 64 | ## Git (Scripting / Automation) 65 | 66 | ### Tooling 67 | 68 | 69 | Ruby 70 | - 71 | 72 | Python 73 | - - Pythonic Git for Humans 74 | 75 | -------------------------------------------------------------------------------- /monos/README.md: -------------------------------------------------------------------------------- 1 | # monos - monorepo / mono source tree tools and (startup) scripts 2 | 3 | 4 | * home :: [github.com/rubycocos/git](https://github.com/rubycocos/git) 5 | * bugs :: [github.com/rubycocos/git/issues](https://github.com/rubycocos/git/issues) 6 | * gem :: [rubygems.org/gems/monos](https://rubygems.org/gems/monos) 7 | * rdoc :: [rubydoc.info/gems/monos](http://rubydoc.info/gems/monos) 8 | * forum :: [opensport](http://groups.google.com/group/opensport) 9 | 10 | 11 | 12 | 13 | ## Usage 14 | 15 | The `mono` (or short `mo`) command line tool lets you run 16 | git commands on multiple repo(sitories) with a single command. 17 | 18 | 19 | 20 | ### Setup 21 | 22 | #### 1) The monorepo (single source tree) root - `MOPATH` 23 | 24 | Use the `MOPATH` environment variable to set the monorepo (single source tree) root 25 | path. The builtin default for now is `/sites`. 26 | 27 | #### 2) The configuration / manifest file to list all repos - `monorepo.yml` 28 | 29 | 30 | Add all repo(sitories) to the `monorepo.yml` that you want 31 | to be part of the "virtual" all-in-one / single mono source tree 32 | in your project. Example: 33 | 34 | ``` yaml 35 | sportdb: 36 | - sport.db 37 | - sport.db.sources 38 | - football.db 39 | 40 | yorobot: 41 | - cache.csv 42 | - sport.db.more 43 | - football.db 44 | - football.csv 45 | 46 | openfootball: 47 | - leagues 48 | - clubs 49 | ``` 50 | 51 | 52 | 53 | ### Commands 54 | 55 | `status` • `fetch` • `sync` • `run` • `env` • `backup` 56 | 57 | ### `status` Command 58 | 59 | Use the `status` command to check for changes (will use `git status --short`) on all repos. Example: 60 | 61 | ``` 62 | $ mono status 63 | $ mono # status is the default command 64 | $ mo status # mo is a "shortcut" convenience alias for mono 65 | $ mo stat 66 | $ mo 67 | ``` 68 | 69 | resulting in something like: 70 | 71 | ``` 72 | 2 change(s) in 9 repo(s) @ 3 org(s) 73 | 74 | -- sportdb@sport.db - CHANGES: 75 | M monos/Manifest.txt 76 | M monos/README.md 77 | M monos/Rakefile 78 | M monos/lib/mono/git/status.rb 79 | M monos/lib/mono/git/sync.rb 80 | M monos/lib/mono/version.rb 81 | RM monos/lib/monoscript.rb -> monos/lib/monos.rb 82 | M monos/test/test_base.rb 83 | ?? monos/bin/ 84 | 85 | -- yorobot@football.csv - CHANGES: 86 | ?? footballdata/ 87 | ``` 88 | 89 | 90 | ### `fetch` Command 91 | 92 | Use the `fetch` command to fetch all (remote) changes (will use `git fetch`) on all existing repos and warn about not-yet-cloned repos. Example: 93 | 94 | ``` 95 | $ mono fetch 96 | $ mo fetch # mo is a "shortcut" convenience alias for mono 97 | ``` 98 | 99 | 100 | 101 | ### `sync` Command 102 | 103 | 104 | Use the `sync` command to sync up (pull) changes (will use `git pull --ff-only`) on all existing repos and `git clone` for new not-yet-cloned repos. Example: 105 | 106 | ``` 107 | $ mono sync 108 | $ mono install # install is an alias for sync 109 | $ mono get # get is another alias for sync 110 | $ mo sync # mo is a "shortcut" convenience alias for mono 111 | $ mo get 112 | ``` 113 | 114 | Note: `install` or `get` or `up` are all aliases that you can use for `sync`. 115 | 116 | 117 | ### `run` Command 118 | 119 | Use the `run` command to run any command in all repos. Example: 120 | 121 | ``` 122 | $ mono run git ls-files 123 | $ mono exec git ls-files # exec is an alias for run 124 | $ mo run git ls-files # mo is a "shortcut" convenience alias for mono 125 | $ mo exec git ls-files 126 | 127 | # -or- 128 | 129 | $ mono run tree 130 | $ mono exec tree 131 | $ mo run tree 132 | $ mo exec tree 133 | ``` 134 | 135 | Note: `exec` is an alias that you can use for `run`. 136 | 137 | 138 | 139 | ### `env` Command 140 | 141 | Use the `env` command to check your `mono` environment setup. 142 | 143 | 144 | 145 | 146 | ### `backup` Command 147 | 148 | Use the `backup` command to backup all repos using 149 | the [`gitti-backup` machinery »](https://github.com/rubycocos/git/tree/master/gitti-backup) 150 | 151 | In a nutshell backup will backup all repos by using 152 | 1. `git clone --mirror` or 153 | 2. `git remote update` (if the local backup already exists) 154 | and store all bare repos (without workspace) in the `~/backup` directory. 155 | 156 | 157 | 158 | That's all for now. 159 | 160 | 161 | 162 | ## Installation 163 | 164 | Use 165 | 166 | gem install monos 167 | 168 | 169 | ## License 170 | 171 | The `monos` scripts are dedicated to the public domain. 172 | Use it as you please with no restrictions whatsoever. 173 | 174 | 175 | ## Questions? Comments? 176 | 177 | Send them along to the 178 | [Open Sports & Friends Forum/Mailing List](http://groups.google.com/group/opensport). 179 | Thanks! 180 | -------------------------------------------------------------------------------- /monos/Rakefile: -------------------------------------------------------------------------------- 1 | require 'hoe' 2 | require './lib/mono/version.rb' 3 | 4 | Hoe.spec 'monos' do 5 | 6 | self.version = Mono::Module::Tool::VERSION 7 | 8 | self.summary = "monos - monorepo / mono source tree tools and (startup) scripts" 9 | self.description = summary 10 | 11 | self.urls = { home: 'https://github.com/rubycocos/git' } 12 | 13 | self.author = 'Gerald Bauer' 14 | self.email = 'opensport@googlegroups.com' 15 | 16 | # switch extension to .markdown for gihub formatting 17 | self.readme_file = 'README.md' 18 | self.history_file = 'CHANGELOG.md' 19 | 20 | self.licenses = ['Public Domain'] 21 | 22 | self.extra_deps = [ 23 | ['monofile', '>= 0.2.2'], 24 | ['gitti', '>= 0.6.1'], 25 | ['gitti-backup', '>= 0.4.1'], 26 | ] 27 | 28 | self.spec_extras = { 29 | required_ruby_version: '>= 2.2.2' 30 | } 31 | end 32 | -------------------------------------------------------------------------------- /monos/bin/mo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ################### 4 | # DEV TIPS: 5 | # 6 | # For local testing run like: 7 | # 8 | # ruby -Ilib bin/mo 9 | # 10 | # Set the executable bit in Linux. Example: 11 | # 12 | # % chmod a+x bin/mo 13 | # 14 | 15 | require 'mono' 16 | 17 | Mono::Tool.main -------------------------------------------------------------------------------- /monos/bin/mono: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ################### 4 | # DEV TIPS: 5 | # 6 | # For local testing run like: 7 | # 8 | # ruby -Ilib bin/mono 9 | # 10 | # Set the executable bit in Linux. Example: 11 | # 12 | # % chmod a+x bin/mono 13 | # 14 | 15 | require 'mono' 16 | 17 | Mono::Tool.main -------------------------------------------------------------------------------- /monos/lib/mono.rb: -------------------------------------------------------------------------------- 1 | require 'monofile' 2 | 3 | 4 | ## first add git support 5 | ## note: use the "modular" version WITHOUT auto-include gitti, 6 | ## thus, require 'gitti/base' (and NOT 'gitti') 7 | require 'gitti/base' 8 | require 'gitti/backup/base' 9 | 10 | 11 | module Mono 12 | ## note: make Git, GitProject, GitRepoSet, etc. available without Gitti:: 13 | include Gitti 14 | 15 | class Tool 16 | include Gitti 17 | end 18 | 19 | ## add more classes e.g. MonoGitProject, etc. - why? why not? 20 | end 21 | 22 | 23 | 24 | 25 | ### 26 | # our own code 27 | require 'mono/version' # let version always go first 28 | require 'mono/base' 29 | require 'mono/experimental' 30 | 31 | require 'mono/commands/status' 32 | require 'mono/commands/fetch' 33 | require 'mono/commands/sync' 34 | require 'mono/commands/env' 35 | require 'mono/commands/backup' 36 | require 'mono/commands/run' 37 | require 'mono/tool' 38 | 39 | 40 | 41 | module Mono 42 | def self.monofile 43 | path = Monofile.find 44 | 45 | if path 46 | Monofile.read( path ) 47 | else 48 | puts "!! WARN: no mono configuration file found; looking for #{Monofile::NAMES.join(', ')} in (#{Dir.getwd})" 49 | Monofile.new ## return empty set -todo/check: return nil - why? why not? 50 | end 51 | end 52 | end ## module Mono 53 | 54 | 55 | 56 | puts Mono::Module::Tool.banner # say hello 57 | -------------------------------------------------------------------------------- /monos/lib/mono/base.rb: -------------------------------------------------------------------------------- 1 | ##################### 2 | # add repo helper 3 | 4 | 5 | class MonoGitHub 6 | def self.clone( name, depth: nil ) 7 | ## lets you use: 8 | ## @rubycoco/gitti or 9 | ## gitti@rubycoco 10 | ## => rubycoco/gitti 11 | 12 | ## note: allow passing in (reusing) of mononames too 13 | mononame = name.is_a?( Mononame ) ? name : Mononame.parse( name ) 14 | path = mononame.real_path 15 | 16 | org_path = File.dirname( path ) 17 | FileUtils.mkdir_p( org_path ) unless Dir.exist?( org_path ) ## make sure path exists 18 | 19 | ### note: use a github clone url (using ssh) like: 20 | ## git@github.com:rubycoco/gitti.git 21 | ssh_clone_url = "git@github.com:#{mononame.to_path}.git" 22 | 23 | Dir.chdir( org_path ) do 24 | Gitti::Git.clone( ssh_clone_url, depth: depth ) 25 | end 26 | end 27 | end 28 | MonoGithub = MonoGitHub ## add convenience (typo?) alias 29 | 30 | 31 | 32 | class MonoGitProject 33 | def self.open( name, &block ) 34 | ## note: allow passing in (reusing) of mononames too 35 | mononame = name.is_a?( Mononame ) ? name : Mononame.parse( name ) 36 | path = mononame.real_path 37 | Gitti::GitProject.open( path, &block ) 38 | end 39 | end 40 | 41 | 42 | 43 | 44 | module Mono 45 | ################# 46 | ## add some short cuts 47 | def self.open( name, &block ) MonoGitProject.open( name, &block ); end 48 | def self.clone( name, depth: nil ) MonoGitHub.clone( name, depth: depth ); end 49 | 50 | ###################################### 51 | ## add some more "porcelain" helpers 52 | def self.sync( name ) 53 | ## add some options - why? why not? 54 | ## - :readonly - auto-adds depth: 1 on clone or such - why? why not? 55 | ## - :clone true/false - do NOT clone only fast forward or such - why? why not? 56 | ## - :clean true/false or similar - only clone repos; no fast forward 57 | ## others - ideas -- ?? 58 | 59 | ## note: allow passing in (reusing) of mononames too 60 | mononame = name.is_a?( Mononame ) ? name : Mononame.parse( name ) 61 | if mononame.exist? 62 | MonoGitProject.open( mononame ) do |proj| 63 | if proj.changes? 64 | puts "!! WARN - local changes in workdir; skipping fast forward (remote) sync / merge" 65 | else 66 | proj.fast_forward ## note: use git pull --ff-only (fast forward only - do NOT merge) 67 | end 68 | end 69 | else 70 | MonoGitHub.clone( mononame ) 71 | end 72 | end # method self.sync 73 | end ## module Mono 74 | 75 | 76 | -------------------------------------------------------------------------------- /monos/lib/mono/commands/backup.rb: -------------------------------------------------------------------------------- 1 | module Mono 2 | 3 | ## pass along hash of repos (e.g. monorepo.yml or repos.yml ) 4 | class Tool 5 | def self.backup 6 | repos = Mono.monofile 7 | 8 | backup = GitBackup.new 9 | 10 | ## step 2: pass in all repos to backup by using 11 | ## 1) git clone --mirror or 12 | ## 2) git remote update (if local backup already exists) 13 | backup.backup( repos ) 14 | end # method backup 15 | end # class Tool 16 | end # module Mono 17 | -------------------------------------------------------------------------------- /monos/lib/mono/commands/env.rb: -------------------------------------------------------------------------------- 1 | module Mono 2 | 3 | class Tool 4 | def self.env ## check environment setup 5 | puts "Mono.root (MOPATH): >#{Mono.root}<" 6 | puts "Mono::Module::Tool.root: >#{Mono::Module::Tool.root}<" 7 | puts 8 | 9 | ## add ruby version and path - why? why not? e.g. 10 | ## ruby: 11 | ## bin: C:/ri330/Ruby2.0.0/bin/ruby.exe 12 | ## version: ruby 2.3.3p222 (2016-11-21 revision 56859) [i386-mingw32] 13 | 14 | puts "git version:" 15 | Git.version 16 | ## Git.config( 'user.name' ) 17 | ## Git.config( 'user.email', show_origin: true ) 18 | 19 | ## dump/print all user.* settings e.g. user.name, user.email 20 | Git.config( /user/, show_origin: true ) 21 | 22 | puts 23 | puts "monofile => (#{Monofile.find}):" 24 | pp Mono.monofile 25 | end 26 | end # class Tool 27 | end # module Mono 28 | -------------------------------------------------------------------------------- /monos/lib/mono/commands/fetch.rb: -------------------------------------------------------------------------------- 1 | module Mono 2 | ## pass along hash of repos (e.g. monorepo.yml or repos.yml ) 3 | class Tool 4 | def self.fetch 5 | repos = Mono.monofile 6 | 7 | count_orgs = 0 8 | count_repos = 0 9 | 10 | total_repos = repos.size 11 | 12 | 13 | repos.each do |org,names| 14 | 15 | org_path = "#{Mono.root}/#{org}" 16 | 17 | names.each do |name| 18 | puts "[#{count_repos+1}/#{total_repos}] #{org}@#{name}..." 19 | 20 | repo = GitHubRepo.new( org, name ) ## owner, name e.g. rubylibs/webservice 21 | 22 | Dir.chdir( org_path ) do 23 | if Dir.exist?( repo.name ) 24 | GitProject.open( repo.name ) do |proj| 25 | proj.fetch 26 | end 27 | else 28 | puts "!! repo not found / missing" 29 | end 30 | end 31 | 32 | count_repos += 1 33 | end 34 | count_orgs += 1 35 | end 36 | 37 | 38 | ## print stats & changes summary 39 | puts 40 | print "#{count_repos} repo(s) @ #{count_orgs} org(s)" 41 | print "\n" 42 | end # method fetch 43 | end # class Tool 44 | end # module Mono 45 | -------------------------------------------------------------------------------- /monos/lib/mono/commands/run.rb: -------------------------------------------------------------------------------- 1 | module Mono 2 | 3 | class Tool 4 | def self.run( *args ) 5 | ## todo/fix: use a "standard" argument to pass along hash of repos 6 | ## (e.g. monorepo.yml or repos.yml ) how? - why? why not? 7 | repos = Mono.monofile 8 | 9 | 10 | cmd = args.join( ' ' ) 11 | 12 | count_orgs = 0 13 | count_repos = 0 14 | 15 | total_repos = repos.size 16 | 17 | repos.each do |org,names| 18 | 19 | org_path = "#{Mono.root}/#{org}" 20 | 21 | names.each do |name| 22 | puts "[#{count_repos+1}/#{total_repos}] #{org}@#{name}..." 23 | 24 | repo = GitHubRepo.new( org, name ) ## owner, name e.g. rubylibs/webservice 25 | 26 | Dir.chdir( org_path ) do 27 | if Dir.exist?( repo.name ) 28 | GitProject.open( repo.name ) do |proj| 29 | proj.run( cmd ) 30 | end 31 | else 32 | puts "!! repo not found / missing" 33 | end 34 | end 35 | 36 | count_repos += 1 37 | end 38 | count_orgs += 1 39 | end 40 | 41 | 42 | ## print stats & changes summary 43 | puts 44 | print "#{count_repos} repo(s) @ #{count_orgs} org(s)" 45 | print "\n" 46 | end # method run 47 | end # class Tool 48 | end # module Mono 49 | -------------------------------------------------------------------------------- /monos/lib/mono/commands/status.rb: -------------------------------------------------------------------------------- 1 | module Mono 2 | 3 | ## pass along hash of repos (e.g. monorepo.yml or repos.yml ) 4 | class Tool 5 | def self.status 6 | repos = Mono.monofile 7 | 8 | changes = [] ## track changes 9 | 10 | count_orgs = 0 11 | count_repos = 0 12 | 13 | total_repos = repos.size 14 | 15 | repos.each do |org,names| 16 | 17 | org_path = "#{Mono.root}/#{org}" 18 | 19 | names.each do |name| 20 | puts "[#{count_repos+1}/#{total_repos}] #{org}@#{name}..." 21 | 22 | repo = GitHubRepo.new( org, name ) ## owner, name e.g. rubylibs/webservice 23 | 24 | Dir.chdir( org_path ) do 25 | if Dir.exist?( repo.name ) 26 | GitProject.open( repo.name ) do |proj| 27 | output = proj.changes 28 | if output.empty? 29 | puts " - no changes -" 30 | else 31 | changes << ["#{org}@#{name}", :CHANGES, output] 32 | end 33 | end 34 | else 35 | puts "!! repo not found / missing" 36 | changes << ["#{org}@#{name}", :NOT_FOUND] 37 | end 38 | end 39 | 40 | count_repos += 1 41 | end 42 | count_orgs += 1 43 | end 44 | 45 | 46 | ## print stats & changes summary 47 | puts 48 | print "#{changes.size} change(s) in " 49 | print "#{count_repos} repo(s) @ #{count_orgs} org(s)" 50 | print "\n" 51 | 52 | changes.each do |item| 53 | puts 54 | print "== #{item[0]} - #{item[1]}" 55 | case item[1] 56 | when :CHANGES 57 | print ":\n" 58 | print item[2] 59 | when :NOT_FOUND 60 | print "\n" 61 | end 62 | end 63 | 64 | end # method status 65 | end # class Tool 66 | end # module Mono 67 | -------------------------------------------------------------------------------- /monos/lib/mono/commands/sync.rb: -------------------------------------------------------------------------------- 1 | module Mono 2 | 3 | ## pass along hash of repos (e.g. monorepo.yml or repos.yml ) 4 | class Tool 5 | def self.sync 6 | repos = Mono.monofile 7 | 8 | count_orgs = 0 9 | count_repos = 0 10 | 11 | total_repos = repos.size 12 | 13 | repos.each do |org,names| 14 | org_path = "#{Mono.root}/#{org}" 15 | FileUtils.mkdir_p( org_path ) unless Dir.exist?( org_path ) ## make sure path exists 16 | 17 | names.each do |name| 18 | puts "[#{count_repos+1}/#{total_repos}] #{org}@#{name}..." 19 | 20 | repo = GitHubRepo.new( org, name ) ## owner, name e.g. rubylibs/webservice 21 | 22 | Dir.chdir( org_path ) do 23 | if Dir.exist?( repo.name ) 24 | GitProject.open( repo.name ) do |proj| 25 | if proj.changes? 26 | puts "!! WARN - local changes in workdir; skipping fast forward (remote) sync / merge" 27 | else 28 | proj.fast_forward ## note: use git pull --ff-only (fast forward only - do NOT merge) 29 | end 30 | end 31 | else 32 | Git.clone( repo.ssh_clone_url ) 33 | end 34 | end 35 | 36 | # 37 | # todo/fix: add (back) error log !!!!!!!!!!!! 38 | # rescue GitError => ex 39 | # puts "!! ERROR: #{ex.message}" 40 | # 41 | # File.open( './errors.log', 'a' ) do |f| 42 | # f.write "#{Time.now} -- repo #{org}/#{name} - #{ex.message}\n" 43 | # end 44 | 45 | count_repos += 1 46 | end 47 | count_orgs += 1 48 | end 49 | 50 | ## print stats 51 | puts "#{count_repos} repo(s) @ #{count_orgs} org(s)" 52 | end # method sync 53 | 54 | end # class Tool 55 | end # module Mono 56 | -------------------------------------------------------------------------------- /monos/lib/mono/experimental.rb: -------------------------------------------------------------------------------- 1 | ############## 2 | # experimental stuff 3 | # 4 | 5 | module Mono 6 | 7 | ###################### 8 | ### lint/print mono (source) tree 9 | ### - check for git repos (via .git/ dir) 10 | # 11 | # turn into 12 | # - tree or 13 | # - lint or 14 | # - doctor or 15 | # - check or such command - why? why not? 16 | def self.walk( path=root) 17 | repos = walk_dir( path ) 18 | repos 19 | end 20 | 21 | 22 | ############### 23 | # private helpers 24 | private 25 | 26 | ## todo/check - use max_depth or max_level or such - why? why not? 27 | def self.walk_dir( path, repos=[], level=1, depth: nil ) 28 | entries = Dir.entries(path) 29 | 30 | ## filter dirs 31 | dirs = entries.select do |entry| 32 | if ['..', '.'].include?( entry ) ## first check for excludes 33 | false 34 | else 35 | full_path = File.join( path, entry ) 36 | File.directory?( full_path ) 37 | end 38 | end 39 | 40 | if dirs.size == 0 ## shortcircuit - no dirs in dir 41 | return repos 42 | end 43 | 44 | repos_count = 0 ## note: local (only) repos count 45 | warns_count = 0 46 | sub_dirs = [] 47 | 48 | 49 | 50 | buf = String.new('') ## use an output buffer (allows optional print) 51 | 52 | 53 | buf << ">#{path}< - level #{level}:\n" 54 | dirs.each do |entry| 55 | next if ['..', '.', '.git'].include?( entry ) 56 | full_path = File.join( path, entry ) 57 | 58 | if Dir.exist?( File.join( full_path, '.git' )) 59 | repos_count += 1 60 | 61 | if level == 1 62 | warns_count += 1 63 | buf << "!! WARN - top-level repo (w/o user/org) >#{entry}< @ #{path}\n" 64 | end 65 | 66 | if level > 2 67 | warns_count += 1 68 | buf << "!! WARN - hidden (?) sub-level #{level} repo (nested too deep?) >#{entry}< @ #{path}\n" 69 | end 70 | 71 | buf << " repo ##{'%-2d' % repos_count} | " 72 | buf << "#{'%-20s' % entry} @ #{File.basename(path)} (#{path})" 73 | buf << "\n" 74 | repos << full_path 75 | 76 | ## check for bare bone git repos - todo/fix: add .gitconfig or such and more - why? why not? 77 | elsif Dir.exist?( File.join( full_path, 'hooks' )) && 78 | Dir.exist?( File.join( full_path, 'info' )) && 79 | Dir.exist?( File.join( full_path, 'objects' )) && 80 | Dir.exist?( File.join( full_path, 'refs' )) 81 | warns_count += 1 82 | buf << "!! WARN - skip bare git repo >#{entry}< @ #{path}\n" 83 | else 84 | buf << " x >#{entry}<\n" 85 | sub_dirs << entry 86 | end 87 | end 88 | buf << " #{repos_count} repos(s), #{dirs.size} dir(s), #{warns_count} warn(s)\n" 89 | buf << "\n" 90 | 91 | ## note: skip output of "plain" diretory listings (no repos, no warnings) 92 | puts buf if repos_count > 0 || warns_count > 0 93 | 94 | 95 | sub_dirs.each do |entry| 96 | ## continue walking 97 | full_path = File.join( path, entry ) 98 | walk_dir( full_path, repos, level+1, depth: depth ) 99 | end 100 | 101 | repos 102 | end 103 | 104 | end # module Mono -------------------------------------------------------------------------------- /monos/lib/mono/tool.rb: -------------------------------------------------------------------------------- 1 | module Mono 2 | 3 | 4 | class Tool 5 | def self.main( args=ARGV ) 6 | 7 | options = {} 8 | OptionParser.new do |parser| 9 | ## note: 10 | ## you can add many/multiple modules 11 | ## e.g. -r gitti -r mono etc. 12 | parser.on( '-r NAME', '--require NAME') do |name| 13 | options[:requires] ||= [] 14 | options[:requires] << name 15 | end 16 | ## todo/fix: 17 | ## add --verbose 18 | ## add -d/--debug 19 | end.parse!( args ) 20 | 21 | 22 | ## add check for auto-require (e.g. ./config.rb) 23 | if options[:requires] ## use custom (auto-)requires 24 | options[:requires].each do |path| 25 | puts "[monofile] auto-require >#{path}<..." 26 | require( path ) 27 | end 28 | else ## use/try defaults 29 | config_path = "./config.rb" 30 | if File.exist?( config_path ) 31 | puts "[monofile] auto-require (default) >#{config_path}<..." 32 | require( config_path ) 33 | end 34 | end 35 | 36 | 37 | ## note: for now assume first argument is command 38 | cmd = if args.size == 0 39 | 'status' ## make status "default" command 40 | else 41 | args.shift ## remove first (head) element 42 | end 43 | 44 | ## note: allow shortcut for commands 45 | case cmd.downcase 46 | when 'status', 'stati', 'stat', 'st', 's' 47 | status 48 | when 'sync', 'syn', 'sy', ## note: allow aliases such as install, get & up too 49 | 'get', 'g', 50 | 'install', 'insta', 'inst', 'ins', 'i', 51 | 'up', 'u' 52 | sync 53 | when 'fetch', 'f' 54 | fetch 55 | when 'env', 'e' 56 | env 57 | when 'backup', 'back', 'b' 58 | backup 59 | when 'run', 'r', 'exec' 60 | run( args ) 61 | 62 | 63 | ################## 64 | ## for debugging / linting 65 | when 'walk' 66 | Mono.walk 67 | else 68 | puts "!! ERROR: unknown command >#{cmd}<" 69 | exit 1 70 | end 71 | 72 | end # method self.main 73 | end # class Tool 74 | 75 | end # module Mono -------------------------------------------------------------------------------- /monos/lib/mono/version.rb: -------------------------------------------------------------------------------- 1 | ## note: use a different module/namespace 2 | ## for the gem version info e.g. MonoCore vs Mono 3 | 4 | module Mono 5 | module Module 6 | module Tool ## todo/check: rename to MonoMeta, MonoModule or such - why? why not? 7 | 8 | MAJOR = 1 ## todo: namespace inside version or something - why? why not?? 9 | MINOR = 1 10 | PATCH = 1 11 | VERSION = [MAJOR,MINOR,PATCH].join('.') 12 | 13 | def self.version 14 | VERSION 15 | end 16 | 17 | def self.banner 18 | "monos/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" 19 | end 20 | 21 | ## note: move root to its own namespace to avoid 22 | ## conflict with Mono.root!!!! 23 | def self.root 24 | File.expand_path( File.dirname(File.dirname(__FILE__) )) 25 | end 26 | 27 | end # module Tool 28 | end # module Module 29 | end # module Mono 30 | 31 | ################################## 32 | # add a convenience shortcut for now - why? why not? 33 | module Mono 34 | VERSION = Mono::Module::Tool::VERSION 35 | end 36 | 37 | -------------------------------------------------------------------------------- /monos/lib/monos.rb: -------------------------------------------------------------------------------- 1 | # note: allow require 'monos' too 2 | # (in addition to require 'mono') 3 | 4 | require_relative './mono' ## todo/check: use just 'mono' - why? why not? 5 | 6 | -------------------------------------------------------------------------------- /monos/sandbox/test_misc.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift( "./lib" ) 2 | require 'mono' 3 | 4 | puts 5 | puts Mono.root 6 | 7 | 8 | puts Monopath.real_path( 'hello.txt@tmp/test' ) 9 | 10 | 11 | puts Monopath.read_utf8( 'hello.txt@tmp/test' ) 12 | pp Monopath.exist?( 'hello.txt@tmp/test' ) 13 | pp Monopath.exist?( 'hola.txt@tmp/test' ) 14 | 15 | 16 | Monopath.open( 'test.txt@tmp/test', 'w:utf-8' ) do |f| 17 | f.write( "test test test\n" ) 18 | f.write( "#{Time.now}\n") 19 | end 20 | 21 | MonoGitProject.open( 'erste-schritte@testgit') do |proj| 22 | puts proj.status 23 | puts proj.changes? 24 | end 25 | 26 | 27 | 28 | Mono.open( 'erste-schritte@testgit') do |proj| 29 | puts proj.status 30 | puts proj.changes? 31 | end 32 | 33 | Mono.root = "/Sites/tmp/#{Time.now.to_i}" 34 | puts Mono.root 35 | 36 | 37 | MonoGitHub.clone( 'fotos@rubycoco') 38 | 39 | MonoGitProject.open( 'fotos@rubycoco' ) do |proj| 40 | puts proj.status 41 | puts proj.changes? 42 | end 43 | 44 | Mono.clone( 'gutenberg@rubycoco', depth: 1 ) 45 | ## Mono.clone( 'fizzubuzzer@rubycoco' ) 46 | -------------------------------------------------------------------------------- /monos/sandbox/test_walk.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift( "./lib" ) 2 | require 'mono' 3 | 4 | puts 5 | puts Mono.root 6 | 7 | 8 | repos = Mono.walk 9 | 10 | puts 11 | puts "#{repos.size} repos:" 12 | puts repos 13 | 14 | puts 15 | puts "bye" 16 | -------------------------------------------------------------------------------- /monos/test/helper.rb: -------------------------------------------------------------------------------- 1 | ## minitest setup 2 | require 'minitest/autorun' 3 | 4 | 5 | ## our own code 6 | require 'mono' 7 | 8 | -------------------------------------------------------------------------------- /monos/test/test_base.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib -I ./test test/test_base.rb 4 | 5 | require 'helper' 6 | 7 | class TestBase < MiniTest::Test 8 | 9 | Git = Mono::Git 10 | 11 | def test_version 12 | puts Mono::Module::Tool::VERSION 13 | puts Mono::Module::Tool.banner 14 | puts Mono::Module::Tool.root 15 | 16 | puts Mono::VERSION 17 | end 18 | 19 | def test_root 20 | puts Mono.root 21 | end 22 | 23 | def test_env 24 | puts Mono::Tool.env 25 | end 26 | 27 | 28 | def test_git_config 29 | puts "---" 30 | Git.config( 'user.name' ) 31 | Git.config( 'user.name', show_origin: true ) 32 | # Git.config( 'user.name', show_scope: true ) 33 | 34 | puts "---" 35 | Git.config( /user/ ) ## note: pass in regex for regex match/search 36 | Git.config( /user/, show_origin: true ) 37 | # Git.config( /user/, show_scope: true ) 38 | 39 | puts "---" 40 | Git.config( /user\./ ) ## note: pass in regex for regex match/search 41 | 42 | puts "---" 43 | ## note: if NOT found Mono::Git.config will exit(1) !!! 44 | ## Mono::Git.config( /proxy/, show_origin: true ) 45 | ## Mono::Git.config( /http/, show_origin: true ) 46 | 47 | puts "---" 48 | end 49 | 50 | end # class TestBase 51 | 52 | -------------------------------------------------------------------------------- /monos/test/test_path.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to run use 3 | # ruby -I ./lib -I ./test test/test_path.rb 4 | 5 | require 'helper' 6 | 7 | class TestPath < MiniTest::Test 8 | 9 | 10 | def test_real_path 11 | [ 12 | '@yorobot/stage/one', 13 | 'one@yorobot/stage', 14 | 'stage/one@yorobot', 15 | ].each do |path| 16 | puts "#{path} => >#{Monopath.parse( path )}<" 17 | 18 | assert_equal "#{Mono.root}/yorobot/stage/one", Monopath.real_path( path ) 19 | end 20 | end 21 | 22 | end # class TestPath 23 | 24 | -------------------------------------------------------------------------------- /yorobot/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /vendor/bundle 26 | /lib/bundler/man/ 27 | 28 | # for a library or gem, you might want to ignore these files since the code is 29 | # intended to run in multiple environments; otherwise, check them in: 30 | # Gemfile.lock 31 | # .ruby-version 32 | # .ruby-gemset 33 | 34 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 35 | .rvmrc 36 | 37 | 38 | -------------------------------------------------------------------------------- /yorobot/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.1 / 2020-10-19 2 | 3 | * Everything is new. First release. 4 | 5 | -------------------------------------------------------------------------------- /yorobot/Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | Manifest.txt 3 | README.md 4 | Rakefile 5 | bin/yo 6 | bin/yorobot 7 | lib/yorobot.rb 8 | lib/yorobot/version.rb 9 | -------------------------------------------------------------------------------- /yorobot/README.md: -------------------------------------------------------------------------------- 1 | # yorobot 2 | 3 | yorobot gem - yo, robot - automate, automate, automate - ready to use scripts and command line tool 4 | 5 | 6 | * home :: [github.com/rubycoco/git](https://github.com/rubycoco/git) 7 | * bugs :: [github.com/rubycoco/git/issues](https://github.com/rubycoco/git/issues) 8 | * gem :: [rubygems.org/gems/yorobot](https://rubygems.org/gems/yorobot) 9 | * rdoc :: [rubydoc.info/gems/yorobot](http://rubydoc.info/gems/yorobot) 10 | 11 | 12 | 13 | ## Usage 14 | 15 | To be done 16 | 17 | 18 | 19 | 20 | ## License 21 | 22 | The `yorobot` scripts are dedicated to the public domain. 23 | Use it as you please with no restrictions whatsoever. 24 | 25 | -------------------------------------------------------------------------------- /yorobot/Rakefile: -------------------------------------------------------------------------------- 1 | require 'hoe' 2 | require './lib/yorobot/version.rb' 3 | 4 | Hoe.spec 'yorobot' do 5 | 6 | self.version = Yorobot::Module::Tool::VERSION 7 | 8 | self.summary = "yorbot gem - yo, robot - automate, automate, automate - ready to use scripts and command line tool" 9 | self.description = summary 10 | 11 | self.urls = { home: 'https://github.com/rubycoco/git' } 12 | 13 | self.author = 'Gerald Bauer' 14 | self.email = 'ruby-talk@ruby-lang.org' 15 | 16 | # switch extension to .markdown for gihub formatting 17 | self.readme_file = 'README.md' 18 | self.history_file = 'CHANGELOG.md' 19 | 20 | self.extra_deps = [ 21 | ['flow-lite', '>= 1.1.0'], 22 | ['gitti', '>= 0.6.1'], 23 | ['hubba', '>= 1.0.0'], 24 | ['hubba-reports', '>= 1.0.1'], 25 | ['monos', '>= 1.1.1'], ## todo/check: just add monofile - why? why not? 26 | ] 27 | 28 | self.licenses = ['Public Domain'] 29 | 30 | self.spec_extras = { 31 | required_ruby_version: '>= 2.2.2' 32 | } 33 | 34 | end 35 | -------------------------------------------------------------------------------- /yorobot/attic/echo.rb: -------------------------------------------------------------------------------- 1 | module Yorobot 2 | 3 | class Echo < Command 4 | def call( *args ) 5 | puts args.join( ' ' ) 6 | end 7 | end # class Echo 8 | 9 | end # module Yorobot 10 | 11 | -------------------------------------------------------------------------------- /yorobot/attic/git.rb: -------------------------------------------------------------------------------- 1 | module Yorobot 2 | 3 | 4 | class Setup < Command 5 | 6 | =begin 7 | # check ssh 8 | if [ ! -d ~/.ssh ]; then mkdir ~/.ssh; fi 9 | echo "$SSH_KEY" > ~/.ssh/id_rsa 10 | chmod 600 ~/.ssh/id_rsa 11 | echo "ssh directory - ~/.ssh:" 12 | ls -la ~/.ssh 13 | # ssh -vT git@github.com 14 | 15 | # check git 16 | git --version 17 | git config --global user.name "Yo Robot" 18 | git config --global user.email "gerald.bauer+yorobot@gmail.com" 19 | git config -l --show-origin 20 | =end 21 | 22 | def call 23 | ############## 24 | ## setup ssh 25 | 26 | ssh_key = ENV['SSH_KEY'] 27 | 28 | if ssh_key.nil? 29 | STDERR.puts "!! ERROR - required SSH_KEY env(ironment) variable missing" 30 | exit 1 31 | end 32 | 33 | ssh_path = File.expand_path( '~/.ssh' ) 34 | 35 | if File.exist?( "#{ssh_path}/id_rsa" ) 36 | STDERR.puts "!! ERROR - ssh key >#{ssh_path}/id_rsa< already exists" 37 | exit 1 38 | end 39 | 40 | ## make sure path exists 41 | FileUtils.mkdir_p( ssh_path ) unless Dir.exist?( ssh_path ) 42 | puts "--> writing ssh key to >#{ssh_path}/id_rsa<..." 43 | File.open( "#{ssh_path}/id_rsa", 'w:utf-8' ) do |f| 44 | f.write( ssh_key ) 45 | end 46 | ## note: ssh key must be "private" only access by owner (otherwise) WILL NOT work 47 | ## res = File.chmod( 0600, "#{ssh_path}/id_rsa" ) 48 | ## puts res ## returns number of files processed; should be 1 - assert - why? why not? 49 | Computer::Shell.run( %Q{chmod 600 #{ssh_path}/id_rsa} ) 50 | 51 | Computer::Shell.run( %Q{ls -la #{ssh_path}} ) 52 | # ssh -vT git@github.com 53 | 54 | 55 | ##### 56 | ## setup git 57 | ## git --version 58 | Git.version 59 | 60 | user_name = ENV['YOROBOT_NAME'] || ENV['YO_NAME'] 61 | user_email = ENV['YOROBOT_EMAIL'] || ENV['YO_EMAIL'] 62 | 63 | Computer::Shell.run( %Q{git config --global user.name "#{user_name}"} ) 64 | Computer::Shell.run( %Q{git config --global user.email "#{user_email}"} ) 65 | 66 | Computer::Shell.run( %Q{git config -l --show-origin} ) 67 | end 68 | end # class Setup 69 | 70 | 71 | 72 | class Clone < Command ## change to SshClone(r) or such - why? why not? 73 | option :depth, "--depth DEPTH", Integer, "shallow clone depth" 74 | 75 | def call( *repos ) 76 | repos.each do |repo| 77 | if options[:depth] 78 | ### shallow "fast clone" - support libraries 79 | ### use https:// instead of ssh - why? why not? 80 | Git.clone( "git@github.com:#{repo}.git", depth: options[:depth] ) 81 | else 82 | ### "deep" standard/ regular clone 83 | Git.clone( "git@github.com:#{repo}.git" ) 84 | end 85 | end 86 | end 87 | end # class Clone 88 | 89 | 90 | 91 | class Push < Command ## change to SshPush(r) or such - why? why not? 92 | 93 | def call( *paths ) ## e.g. "./cache.github" etc. 94 | msg = "auto-update week #{Date.today.cweek}" 95 | 96 | paths.each do |path| 97 | GitProject.open( path ) do |proj| 98 | if proj.changes? 99 | proj.add( "." ) 100 | proj.commit( msg ) 101 | proj.push 102 | end 103 | end 104 | end 105 | end 106 | end # class Push 107 | 108 | 109 | end # module Yorobot 110 | -------------------------------------------------------------------------------- /yorobot/attic/github.rb: -------------------------------------------------------------------------------- 1 | module Yorobot 2 | 3 | class Github < Command ## change to GithubStats or such - why? why not? 4 | 5 | ## todo/check: use --data-dir/--datadir - why? why not? 6 | option :data_dir, "-d DIR", "--dir DIR", 7 | "data dir (defaults to #{Hubba.config.data_dir})" 8 | 9 | ## todo/check: add switch --[no]-traffic - why? why not? 10 | 11 | 12 | def call( username ) 13 | ## username e.g. geraldb 14 | 15 | if options[:data_dir] ## e.g. "./cache.github" 16 | puts " setting data_dir to >#{options[:data_dir]}<" 17 | Hubba.config.data_dir = options[:data_dir] 18 | end 19 | 20 | h = Hubba.reposet( username ) ## note: do NOT include yorobot for now 21 | pp h 22 | 23 | Hubba.update_stats( h ) 24 | Hubba.update_traffic( h ) 25 | puts "Done." 26 | end 27 | 28 | end # class Github 29 | end # module Yorobot 30 | 31 | -------------------------------------------------------------------------------- /yorobot/attic/list.rb: -------------------------------------------------------------------------------- 1 | module Yorobot 2 | 3 | class List < Command 4 | def call 5 | ## list all known commands 6 | commands = Commands.commands 7 | puts "#{commands.size} command(s):" 8 | commands.each do |name, command| 9 | print " %-10s" % name 10 | print " | #{command.class.name} (#{command.name})" 11 | print "\n" 12 | end 13 | end 14 | end 15 | 16 | end # module Yorobot 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /yorobot/bin/yo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ################### 4 | # DEV TIPS: 5 | # 6 | # For local testing run like: 7 | # 8 | # ruby -Ilib bin/yo 9 | # 10 | # Set the executable bit in Linux. Example: 11 | # 12 | # % chmod a+x bin/yo 13 | # 14 | 15 | require 'yorobot' 16 | 17 | Yorobot::Tool.main 18 | -------------------------------------------------------------------------------- /yorobot/bin/yorobot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ################### 4 | # DEV TIPS: 5 | # 6 | # For local testing run like: 7 | # 8 | # ruby -Ilib bin/yorobot 9 | # 10 | # Set the executable bit in Linux. Example: 11 | # 12 | # % chmod a+x bin/yorobot 13 | # 14 | 15 | require 'yorobot' 16 | 17 | Yorobot::Tool.main 18 | -------------------------------------------------------------------------------- /yorobot/lib/yorobot.rb: -------------------------------------------------------------------------------- 1 | require 'flow-lite' 2 | 3 | 4 | #### 5 | # add more 3rd party gems / libs to flow "prologue / prelude" 6 | # 7 | # require 'computer' # add shell run/call etc. machinery 8 | # add via gitti & hubba 9 | require 'gitti' 10 | require 'hubba' 11 | require 'hubba/reports' 12 | require 'mono' 13 | 14 | 15 | # our own code 16 | require 'yorobot/version' # note: let version always go first 17 | 18 | 19 | 20 | #### add predefined steps 21 | module Flow 22 | class Base 23 | 24 | =begin 25 | # check ssh 26 | if [ ! -d ~/.ssh ]; then mkdir ~/.ssh; fi 27 | echo "$SSH_KEY" > ~/.ssh/id_rsa 28 | chmod 600 ~/.ssh/id_rsa 29 | echo "ssh directory - ~/.ssh:" 30 | ls -la ~/.ssh 31 | # ssh -vT git@github.com 32 | 33 | # check git 34 | git --version 35 | git config --global user.name "Yo Robot" 36 | git config --global user.email "gerald.bauer+yorobot@gmail.com" 37 | git config -l --show-origin 38 | =end 39 | def step_adduser 40 | ############## 41 | ## setup ssh 42 | 43 | ssh_key = ENV['SSH_KEY'] 44 | 45 | if ssh_key.nil? 46 | STDERR.puts "!! ERROR - required SSH_KEY env(ironment) variable missing" 47 | exit 1 48 | end 49 | 50 | ssh_path = File.expand_path( '~/.ssh' ) 51 | 52 | if File.exist?( "#{ssh_path}/id_rsa" ) 53 | STDERR.puts "!! ERROR - ssh key >#{ssh_path}/id_rsa< already exists" 54 | exit 1 55 | end 56 | 57 | ## make sure path exists 58 | FileUtils.mkdir_p( ssh_path ) unless Dir.exist?( ssh_path ) 59 | puts "--> writing ssh key to >#{ssh_path}/id_rsa<..." 60 | File.open( "#{ssh_path}/id_rsa", 'w:utf-8' ) do |f| 61 | f.write( ssh_key ) 62 | end 63 | ## note: ssh key must be "private" only access by owner (otherwise) WILL NOT work 64 | ## res = File.chmod( 0600, "#{ssh_path}/id_rsa" ) 65 | ## puts res ## returns number of files processed; should be 1 - assert - why? why not? 66 | Computer::Shell.run( %Q{chmod 600 #{ssh_path}/id_rsa} ) 67 | 68 | Computer::Shell.run( %Q{ls -la #{ssh_path}} ) 69 | # ssh -vT git@github.com 70 | 71 | 72 | ##### 73 | ## setup git 74 | ## git --version 75 | Git.version 76 | 77 | user_name = ENV['YOROBOT_NAME'] || ENV['YO_NAME'] || 'Yo Robot' 78 | user_email = ENV['YOROBOT_EMAIL'] || ENV['YO_EMAIL'] || 'gerald.bauer+yorobot@gmail.com' 79 | 80 | Computer::Shell.run( %Q{git config --global user.name "#{user_name}"} ) 81 | Computer::Shell.run( %Q{git config --global user.email "#{user_email}"} ) 82 | 83 | Computer::Shell.run( %Q{git config -l --show-origin} ) 84 | end 85 | 86 | end # class Base 87 | end # module Flow 88 | 89 | 90 | 91 | module Yorobot 92 | class Tool 93 | def self.main( args=ARGV ) 94 | 95 | ## setup/check mono root 96 | puts "[flow] pwd: #{Dir.pwd}" 97 | 98 | 99 | ## quick hack: 100 | ## if /sites does not exists 101 | ## assume running with GitHub Actions or such 102 | ## and use working dir as root? or change to home dir ~/ or ~/mono - why? why not? 103 | ## 104 | ## in the future use some -e/-env(ironemt) settings and scripts - why? why not? 105 | if Dir.exist?( 'C:/Sites' ) 106 | Mono.root = 'C:/Sites' ## use local (dev) setup for testing flow steps 107 | 108 | puts "[flow] assume local (dev) setup for testing" 109 | else 110 | Mono.root = Dir.pwd 111 | 112 | ## for debugging print / walk mono (source) tree 113 | Mono.walk 114 | end 115 | puts "[flow] Mono.root: #{Mono.root}" 116 | 117 | 118 | ## pass along to "standard" flow engine 119 | ::Flow::Tool.main( args ) 120 | end 121 | end # class Tool 122 | end # module Yorobot 123 | 124 | 125 | puts Yorobot::Module::Tool.banner # say hello 126 | -------------------------------------------------------------------------------- /yorobot/lib/yorobot/version.rb: -------------------------------------------------------------------------------- 1 | 2 | module Yorobot 3 | module Module 4 | module Tool 5 | MAJOR = 1 ## todo: namespace inside version or something - why? why not?? 6 | MINOR = 1 7 | PATCH = 0 8 | VERSION = [MAJOR,MINOR,PATCH].join('.') 9 | 10 | def self.version 11 | VERSION 12 | end 13 | 14 | def self.banner 15 | "yorobot/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" 16 | end 17 | 18 | def self.root 19 | File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) ) 20 | end 21 | 22 | end # module Tool 23 | end # module Module 24 | end # module Yorobot 25 | 26 | -------------------------------------------------------------------------------- /yorobot/sandbox/Flowfile: -------------------------------------------------------------------------------- 1 | puts "hello from flowfile" 2 | 3 | step :echo do 4 | puts "hello from echo" 5 | end 6 | 7 | -------------------------------------------------------------------------------- /yorobot/sandbox/test_run.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # to test run: 3 | # ruby sandbox/test_run.rb -f sandbox/Flowfile 4 | 5 | 6 | $LOAD_PATH.unshift( File.expand_path( './lib' ) ) 7 | puts "LOAD_PATH:" 8 | puts $LOAD_PATH 9 | puts 10 | require 'yorobot' 11 | 12 | puts Hubba::VERSION 13 | puts HubbaReports::VERSION 14 | 15 | 16 | Yorobot::Tool.main 17 | Yorobot::Tool.main( ['-f', 'sandbox/Flowfile' ] ) 18 | Yorobot::Tool.main( ['-f', 'sandbox/Flowfile', 'echo' ] ) 19 | 20 | --------------------------------------------------------------------------------