├── .gitignore ├── .rspec ├── .travis.yml ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin └── github-copywriter ├── github-copywriter.gemspec ├── lib ├── github-copywriter.rb └── github-copywriter │ ├── octikit_wrapper.rb │ ├── regex.rb │ └── version.rb ├── spec ├── accepted_name_spec.rb ├── copyright_regex_spec.rb └── spec_helper.rb └── test_repo_demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Added by bundler 2 | /.bundle/ 3 | /.yardoc 4 | /Gemfile.lock 5 | /_yardoc/ 6 | /coverage/ 7 | /doc/ 8 | /pkg/ 9 | /spec/reports/ 10 | /tmp/ 11 | *.bundle 12 | *.so 13 | *.o 14 | *.a 15 | mkmf.log 16 | 17 | # Jekyll build 18 | _site 19 | 20 | # Vim 21 | .*.swp 22 | .*.swo 23 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | # Ruby versions from: https://www.ruby-lang.org/en/downloads/ 4 | # Viewed on January 12, 2016 5 | 6 | - 2.3.0 # Current Stable 7 | - 2.2.4 # Previous Stable 8 | - 2.1.8 # Old Stable 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | ### Introduction 4 | I'm pretty much open to anything really. If you have a feature you want to 5 | implement, first create an issue for it (so I can see what you're trying to 6 | achieve). Then submit a pull request later when you're done with it. 7 | 8 | Or, if you're feeling generous, implement someone else's feature request! 9 | 10 | Try best you can to keep the code consistent. My only gripe is indentation. 11 | Please set your editor to use 4-space indentation! 12 | 13 | If I merge any of your pull requests, big or small, I will add you to the 14 | [list of contributors](//github.com/ryanmjacobs/github-copywriter/blob/master/CONTRIBUTORS.md) 15 | with a small note of what you did. If you want to update it to 16 | include your website and/or email, just submit a PR. 17 | 18 | ### Setup 19 | To start hacking away, fork and clone the project. 20 | ``` 21 | $ git clone https://github.com/YourUsername/github-copywriter 22 | $ cd github-copywriter 23 | ``` 24 | 25 | ### Adding features and fixing bugs 26 | Create an aptly named branch off the master. 27 | ``` 28 | $ git checkout master 29 | $ git checkout -b add-rainbow-text 30 | ``` 31 | 32 | Make some modifications/additions. 33 | ``` 34 | $ do some work... 35 | $ add some commits... 36 | ``` 37 | 38 | Push changes to the origin. 39 | ``` 40 | git push origin add-rainbow-text 41 | ``` 42 | 43 | Lastly, submit a pull request! 44 | 45 | ### Staying in sync with the upstream 46 | Set and fetch the upstream remote. 47 | ``` 48 | $ git remote add upstream https://github.com/ryanmjacobs/github-copywriter 49 | $ git fetch upstream 50 | ``` 51 | 52 | And every once in a while sync your `master` to `upstream/master`. 53 | ``` 54 | $ git fetch upstream 55 | $ git checkout master 56 | $ git merge upstream/master 57 | $ git push origin master 58 | ``` 59 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | ## Contributors 2 | 3 | * Ryan Jacobs ([ryanmjacobs](//github.com/ryanmjacobs)) 4 | * Pavel Dostál ([pdostal](//github.com/pdostal)) 5 | 6 | Want to be added to this list? Checkout 7 | [CONTRIBUTING.md](//github.com/ryanmjacobs/github-copywriter/blob/master/CONTRIBUTING.md) 8 | for more info. 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Specify your gem's dependencies in github-copywriter.gemspec 4 | gemspec 5 | gem "octokit" 6 | gem "colorize" 7 | 8 | group :test do 9 | gem "rspec" 10 | gem "rake" 11 | end 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Ryan Jacobs 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # github-copywriter 2 | [![Build Status](https://travis-ci.org/ryanmjacobs/github-copywriter.svg?branch=master)](https://travis-ci.org/ryanmjacobs/github-copywriter) 3 | [![Gem Version](https://badge.fury.io/rb/github-copywriter.svg)](http://badge.fury.io/rb/github-copywriter) 4 | 5 | ![test-repo demo](https://raw.githubusercontent.com/ryanmjacobs/github-copywriter/master/test_repo_demo.gif) 6 | 7 | **See the above commit [here](//github.com/ryanmjacobs/test-repo/commit/7b095e65adf4cedcd7891326ec32c5f526838dd5). Or, see a more complex example [here](https://github.com/ryanmjacobs/test-repo/commit/7fbc410ce1171a2e858972b1c7cfed32b86d98e2).** 8 | 9 | ## Updates your copyrights... so you don't have to! 10 | 11 | ``` 12 | $ gem install github-copywriter 13 | ``` 14 | Checkout usage information [here](//ryanmjacobs.github.io/github-copywriter)! 15 | 16 | ## Contributing 17 | Please submit issues or feature requests [here](//github.com/ryanmjacobs/github-copywriter/issues). 18 | Questions and comments are welcome as well. Checkout 19 | [CONTRIBUTING.md](//github.com/ryanmjacobs/github-copywriter/blob/master/CONTRIBUTING.md) 20 | for more info. 21 | 22 | Feel free to [fork](//github.com/ryanmjacobs/github-copywriter/fork) this project, and: 23 | 24 | * fix bugs 25 | * add some sweet features 26 | * implement feature requests 27 | * improve the docs and/or this site 28 | 29 | ## Under the hood 30 | All GitHub API calls are made with [Octokit](//github.com/octokit/octokit.rb). 31 | 32 | ####Basic breakdown of the program's logic: 33 | 1. Authenticate to GitHub. 34 | 2. Loop through each user repo given, and: 35 | * Update copyrights on files: `README.md`, `LICENSE`, etc. 36 | * Create a local commit and push to GitHub. 37 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /bin/github-copywriter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ################################################################################ 3 | # github-copywriter 4 | # 5 | # Author: Ryan Jacobs 6 | ################################################################################ 7 | 8 | require "optparse" 9 | require "highline/import" 10 | require "github-copywriter" 11 | 12 | # To store command-line options/parameters 13 | options = {} 14 | 15 | # Grab command-line options/parameters 16 | ARGV << "-h" if ARGV.empty? 17 | OptionParser.new do |opts| 18 | executable_name = File.split($0)[1] 19 | opts.banner = <<-EOS 20 | Updates your copyrights... so you don't have to! 21 | 22 | Usage: #{executable_name} [repos...] 23 | #{executable_name} --all 24 | 25 | EOS 26 | 27 | opts.on("-a", "--all", "Update ALL repositories.") do 28 | options[:all_repos] = true 29 | end 30 | 31 | opts.on("-y", "--year YEAR", "Update copyrights to a given year.") do |year| 32 | year = year.to_i 33 | 34 | if year >= 1000 && year <= 9999 then 35 | options[:year] = year 36 | else 37 | puts "error: year must be four digits".red 38 | exit 39 | end 40 | end 41 | 42 | opts.on("-s", "--skip-forks", "Don't update forked repos.") do 43 | options[:skip_forks] = true 44 | end 45 | 46 | opts.on("-n", "--dry-run", "Don't actually write to any repos.") do 47 | options[:dry_run] = true 48 | end 49 | 50 | opts.on("-b", "--branches branch1,branch2", "Update specific branches only.") do |comma_separated_branches| 51 | options[:branches] = comma_separated_branches.split(",").map(&:strip) 52 | end 53 | 54 | opts.on_tail("-h","--help", "Show this message.") do 55 | puts opts 56 | puts "" 57 | exit 58 | end 59 | 60 | opts.on_tail("-v", "--version", "Print the version.") do 61 | puts "github-copywriter v#{Copywriter::VERSION}" 62 | exit 63 | end 64 | end.parse! 65 | 66 | # Must have at least one repo! 67 | if ARGV.empty? and not options[:all_repos] 68 | puts "error: No repositories supplied!".red 69 | exit 70 | end 71 | 72 | # Pass input repos into options 73 | options[:repos] = ARGV 74 | 75 | # Get username and password 76 | puts "Obtaining OAuth2 access_token from github." 77 | username = ask("GitHub username: ") { |q| q.echo = true } 78 | password = ask("GitHub password: ") { |q| q.echo = false } 79 | 80 | # Auth to GitHub 81 | cw = Copywriter.new(username, password) 82 | 83 | # Do it! 84 | cw.run!(options) 85 | -------------------------------------------------------------------------------- /github-copywriter.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | lib = File.expand_path("../lib", __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require "github-copywriter/version" 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "github-copywriter" 8 | gem.version = Copywriter::VERSION 9 | gem.authors = ["Ryan Jacobs"] 10 | gem.email = ["ryan.mjacobs@gmail.com"] 11 | gem.summary = %q{github-copywriter updates your copyrights... so you don't have to!} 12 | gem.description = %q{github-copywriter scans through your repositories and updates any copyrights it finds.} 13 | gem.homepage = "http://ryanmjacobs.github.io/github-copywriter" 14 | gem.license = "MIT" 15 | 16 | gem.files = `git ls-files -z`.split("\x0") - %w(test_repo_demo.gif) 17 | gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 19 | gem.require_paths = ["lib"] 20 | 21 | gem.required_ruby_version = ">= 2.1.8" 22 | 23 | gem.add_development_dependency "bundler", "~> 1.6" 24 | gem.add_development_dependency "rake", "~> 10.0" 25 | gem.add_development_dependency "rspec", "~> 3.1" 26 | 27 | gem.add_runtime_dependency "octokit", "~> 3.7" 28 | gem.add_runtime_dependency "colorize", "~> 0.7" 29 | gem.add_runtime_dependency "highline", "~> 1.6" 30 | end 31 | -------------------------------------------------------------------------------- /lib/github-copywriter.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | ################################################################################ 3 | # github-copywriter/github-copywriter.rb 4 | # 5 | # Author: Ryan Jacobs 6 | ################################################################################ 7 | 8 | require "github-copywriter/regex" 9 | require "github-copywriter/version" 10 | require "github-copywriter/octikit_wrapper" 11 | 12 | require "base64" 13 | require "octokit" 14 | require "colorize" 15 | 16 | class Copywriter 17 | 18 | COMMIT_MSG = "Update copyright. ♥ github-copywriter\n\nFor more info, visit http://ryanmjacobs.github.io/github-copywriter" 19 | 20 | def initialize(username, password) 21 | # Auth to GitHub 22 | Octokit.auto_paginate = true 23 | @client = Octokit::Client.new(:login => username, :password => password) 24 | 25 | # Check if the basic_auth worked. 26 | # TODO: Find a better way to do this 27 | # https://github.com/ryanmjacobs/github-copywriter/issues/1 28 | begin 29 | @client.authorizations 30 | rescue Octokit::Unauthorized 31 | puts "error: Bad credentials.".red 32 | exit 33 | rescue Faraday::ConnectionFailed 34 | puts "error: Connection failed.".red 35 | exit 36 | end 37 | end 38 | 39 | def run!(options={}) 40 | # Default options 41 | options = { 42 | all_repos: false, 43 | skip_forks: false, 44 | dry_run: false, 45 | year: nil, 46 | branches: nil 47 | }.merge(options) 48 | 49 | if options[:all_repos] then 50 | repos = @client.repositories() 51 | else 52 | repos = Array.new 53 | options[:repos].each do |r| 54 | name = @client.login+"/"+File.basename(r) 55 | if @client.repository?(name) then 56 | repos << @client.repository(name) 57 | else 58 | puts "error: repo \"#{name}\" does not exist!".red 59 | exit 60 | end 61 | end 62 | end 63 | 64 | # Get copyright year 65 | cur_year = options[:year] || Time.now.year 66 | 67 | # Loop through each repo 68 | repos.each do |repo| 69 | 70 | # Skip if repo is a fork and --skip-forks is on 71 | next if options[:skip_forks] and repo[:fork] 72 | 73 | # Repo name 74 | repo_name = repo[:full_name] 75 | puts "\n#{repo_name}:".cyan 76 | 77 | # Repo refs (branches) to loop through 78 | if options[:branches] == nil then 79 | # Get every single ref (branch) by default 80 | refs = @client.refs(repo_name, "heads").map { |r| r[:ref].sub("refs/", "") } 81 | else 82 | # User-supplied branches 83 | refs = options[:branches].map { |branch| "heads/#{branch}" } 84 | end 85 | 86 | # Loop through each ref 87 | refs.each do |ref| 88 | puts " #{ref}:".yellow 89 | 90 | # Grab the *whole* tree 91 | begin 92 | latest_commit = @client.ref(repo_name, ref).object 93 | root_tree = @client.commit(repo_name, latest_commit.sha).commit.tree 94 | whole_tree = @client.tree(repo_name, root_tree.sha, :recursive => true) 95 | rescue Octokit::NotFound 96 | puts "error: ref #{ref} not found.".red 97 | exit 98 | end 99 | 100 | # Warn the user about truncation 101 | if whole_tree[:truncated] then 102 | puts " warning: tree truncated because number of items exceeded limit.".yellow 103 | puts " If you feel like fixing this, see issue #xx".yellow 104 | puts " http://github.com/ryanmjacobs/github-copywriter/xx".yellow 105 | end 106 | 107 | # Stores updated files until we commit 108 | # @modified_files is a hash {:path, :content} 109 | @modified_files = Array.new 110 | 111 | # Loop through all of whole_tree's blobs 112 | whole_tree[:tree].each do |file| 113 | next if file[:type] != "blob" 114 | 115 | if Copywriter::Regex.accepted_name?(file[:path]) then 116 | # Grab file from repo 117 | file = @client.contents(repo_name, :path => file[:path], :ref => ref) 118 | if file[:type] != "file" then raise "error: #{file_path} on #{repo} is not a file!" end 119 | 120 | # Update the copyright 121 | new_content = Copywriter::Regex.update_copyright(cur_year, Base64.decode64(file[:content])) 122 | 123 | # Skip to the next file if we didn't even find a copyright in the text 124 | next if new_content[:copyrights_found] == 0 125 | 126 | # Add to list of files to commit, only if the file has changed 127 | if new_content[:copyrights_updated] > 0 then 128 | @modified_files << {:path => file[:path], :content => new_content[:content]} 129 | puts " #{file[:path]} is now up-to-date.".green 130 | else 131 | puts " #{file[:path]} is already up-to-date." 132 | end 133 | end 134 | end 135 | 136 | # Commit stored up files 137 | if @modified_files.size > 0 then 138 | if @modified_files.size == 1 then 139 | print " Committing 1 file..." 140 | else 141 | print " Committing #{@modified_files.size} files..." 142 | end 143 | 144 | if options[:dry_run] then 145 | puts " refusing to commit because of --dry-run". 146 | else 147 | commit_files(repo_name, ref, "100644", @modified_files, COMMIT_MSG) 148 | puts " done!" 149 | end 150 | else 151 | puts " No files needed to be commited." 152 | end 153 | end 154 | end 155 | end 156 | end 157 | -------------------------------------------------------------------------------- /lib/github-copywriter/octikit_wrapper.rb: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # github-copywriter/octikit_wrapper.rb 3 | # 4 | # Octokit wrapper for commiting multiple files to a GitHub repo. 5 | # 6 | # Author: Ryan Jacobs 7 | ################################################################################ 8 | 9 | class Copywriter 10 | private 11 | # Commits files to a GitHub repo. 12 | # Requires @client to be already authorized with Copywriter.login! 13 | # 14 | # @param repo Repo to commit to, e.g. "user/repo_name" 15 | # @param ref Branch to commit to, e.g. "heads/master" 16 | # @param file_mode Filemode on repo, e.g. "100644" 17 | # @param files Array of {:path, :content} 18 | # @param commit_msg Commit message, e.g. "Update file.rb" 19 | # 20 | # @example 21 | # commit_files("user/repo", "heads/master", "100644", [{:path => "file.md", :content => "files content}], "modify file.md") 22 | def commit_files(repo, ref, file_mode, files, commit_msg) 23 | # Return if we don't have any files to commit 24 | if files.size == 0 then return false end 25 | 26 | # Force file_mode to be either 100644 or 100775 27 | if file_mode != "100644" or file_mode != "100775" then 28 | file_mode = "100644" 29 | end 30 | 31 | begin 32 | # Construct temp. tree of files to commit 33 | trees = Array.new 34 | files.each do |file| 35 | blob_sha = @client.create_blob(repo, Base64.encode64(file[:content]), "base64") 36 | 37 | trees << { 38 | :path => file[:path], 39 | :mode => file_mode, 40 | :type => "blob", 41 | :sha => blob_sha 42 | } 43 | end 44 | 45 | # Contruct the commit 46 | latest_commit = @client.ref(repo, ref).object 47 | base_tree = @client.commit(repo, latest_commit.sha).commit.tree 48 | new_tree = @client.create_tree(repo, trees, :base_tree => base_tree.sha) 49 | new_commit = @client.create_commit(repo, commit_msg, new_tree.sha, latest_commit.sha) 50 | 51 | # Commit it! 52 | @client.update_ref(repo, ref, new_commit.sha) 53 | return true 54 | rescue 55 | return false 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/github-copywriter/regex.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | ################################################################################ 3 | # github-copywriter/regex.rb 4 | # 5 | # All of the regex that's behind this amazing project :) 6 | # 7 | # Author: Ryan Jacobs 8 | ################################################################################ 9 | 10 | class Copywriter 11 | module Regex 12 | extend self 13 | 14 | # Returns true if this is a file that we will update, (otherwise false). 15 | # 16 | # @return [Boolean] True if name is accepted. False if otherwise. 17 | def accepted_name?(filename) 18 | filename = File.basename(filename) 19 | 20 | names = ["readme", "license"] 21 | extensions = [".md", ".txt", ".html"] 22 | 23 | if names.include? filename.downcase then return true end 24 | if extensions.include? File.extname(filename.downcase) then return true end 25 | 26 | return false 27 | end 28 | 29 | # Updates copyright using regex. 30 | # 31 | # @param year [String] Year to update to, e.g. "2024" 32 | # @param content [String] Text with outdated copyright 33 | # @return [Hash] {:new_content, :copyrights_found, :copyrights_updated} 34 | # 35 | # Example return hashes: 36 | # ====================== 37 | # 38 | # Input -- an out-of-date copyright: 39 | # "...Copyright 2013..." 40 | # Return: 41 | # {:content => "...Copyright 2014...", :copyrights_found => 1, :copyrights_updated => 1} 42 | # 43 | # Input -- an already up-to-date copyright: 44 | # "...Copyright 2014..." 45 | # Return: 46 | # {:content => "...Copyright 2014...", :copyrights_found => 1, :copyrights_updated => 0} 47 | # 48 | # Input -- no copyright whatsoever: 49 | # "...no copyright here..." 50 | # Return: 51 | # {:content => "...no copyright here...", :copyrights_found => 0, :copyrights_updated => 0} 52 | def update_copyright(year, old_content) 53 | data = { 54 | :content => "", 55 | :copyrights_found => 0, 56 | :copyrights_updated => 0 57 | } 58 | 59 | # Convert the strings to unicode 60 | old_content.force_encoding "utf-8" 61 | 62 | # All teh regex 63 | prefix = /(copyright( \(c\)| ©)?|\(c\)|©) /i 64 | suffix = /(\.| |$)/ 65 | comma_sep = /((\d{4},|\d{4}-\d{4},)*)/ 66 | regexs = [ 67 | # Singular -> (c) 2012 -> (c) 2014 68 | # for any year 69 | { :regex => /#{prefix}\d{4}#{suffix}/, :replace => "\\1 #{year}\\3" }, 70 | 71 | # Multiple comma separated, ending w/ year_before 72 | # (c) 2009-2011,2013 -> (c) 2009-2011,2013-2014 73 | # for year before 74 | { :regex => /#{prefix}#{comma_sep}#{year-1}#{suffix}/, :replace => "\\1 \\3#{year-1}-#{year}\\5" }, 75 | 76 | # Multiple comma separated, ending w/ some_year DASH year_before 77 | # (c) 2008-2010,2012-2013 -> (c) 2008-2010,2012-2014 78 | # for year before 79 | { :regex => /#{prefix}#{comma_sep}((\d{4}-)?#{year-1})#{suffix}/, :replace => "\\1 \\3\\6#{year}\\7" }, 80 | 81 | # Multiple comma separated with dash 82 | # (c) 2009,2012 -> (c) 2009,2012,2014 83 | # (c) 2009-2012 -> (c) 2009-2012,2014 84 | # for any year 85 | { :regex => /#{prefix}#{comma_sep}((\d{4}-)?\d{4})#{suffix}/, :replace => "\\1 \\3\\5,#{year}\\7" }, 86 | ] 87 | 88 | already_updated = /#{prefix}#{comma_sep}(\d{4}-)?#{year}#{suffix}/ 89 | has_copyright = /#{prefix}#{comma_sep}(\d{4}-)?\d{4}#{suffix}/ 90 | 91 | # Loop through each line of the input text 92 | old_content.lines.each do |line| 93 | # Is there even a copyright? If there isn't, goto the next line. 94 | if not (has_copyright === line) then 95 | data[:content] << line 96 | next 97 | end 98 | 99 | # Are we already updated? If so, goto the next line. 100 | if (already_updated === line) then 101 | data[:copyrights_found] += 1 102 | data[:content] << line 103 | next 104 | end 105 | 106 | # Loop through our regex until we get one to work 107 | regexs.each do |r| 108 | data[:copyrights_found] += 1 if (r[:regex] === line) 109 | updated_line = line.gsub(r[:regex], r[:replace]) 110 | 111 | # Did the regex update the copyright? If so, goto the next line. 112 | if updated_line != line then 113 | data[:content] << updated_line 114 | data[:copyrights_updated] += 1 115 | break 116 | end 117 | end 118 | end 119 | 120 | return data 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /lib/github-copywriter/version.rb: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # github-copywriter/version.rb 3 | # 4 | # Author: Ryan Jacobs 5 | ################################################################################ 6 | 7 | class Copywriter 8 | 9 | # Current version 10 | # @return [String] 11 | VERSION = "1.2.4".freeze 12 | 13 | end 14 | -------------------------------------------------------------------------------- /spec/accepted_name_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | ### 4 | # This spec desperately needs a rewrite. 5 | # It works, but the display is all wrong 6 | # and unreadable. 7 | # 8 | # (Sorry, I'm still learning how to format my rpecs.) 9 | ### 10 | 11 | describe Copywriter do 12 | regex = Copywriter::Regex 13 | 14 | # Accepted 15 | context "a filename matching README, LICENSE, or with the extensions .md, .txt, or .html" do 16 | it { expect(regex.accepted_name? "README") .to be(true) } 17 | it { expect(regex.accepted_name? "LICENSE") .to be(true) } 18 | it { expect(regex.accepted_name? "README.md") .to be(true) } 19 | it { expect(regex.accepted_name? "LICENSE.md") .to be(true) } 20 | 21 | it { expect(regex.accepted_name? "whatever.md") .to be(true) } 22 | it { expect(regex.accepted_name? "thing.html") .to be(true) } 23 | end 24 | 25 | # Not Accepted 26 | context "a filename NOT matching README, LICENSE, or with the extensions .md, .txt, or .html" do 27 | it { expect(regex.accepted_name? "garbage") .to be(false) } 28 | it { expect(regex.accepted_name? "colors.py") .to be(false) } 29 | it { expect(regex.accepted_name? "list_files.c") .to be(false) } 30 | it { expect(regex.accepted_name? "coolLib.js") .to be(false) } 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/copyright_regex_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require "spec_helper" 3 | 4 | ### This script assumes the current year is 2014. ### 5 | 6 | ### 7 | # This spec desperately needs a rewrite. 8 | # It works, but the display is all wrong 9 | # and unreadable. 10 | # 11 | # (Sorry, I'm still learning how to format my rpecs.) 12 | ### 13 | 14 | copyrights = [ 15 | { :old => "Copyright 1970", :new => "Copyright 2014" }, 16 | { :old => "copyright 1970", :new => "copyright 2014" }, 17 | 18 | { :old => "Copyright (C) 1970", :new => "Copyright (C) 2014" }, 19 | { :old => "copyright (c) 1970", :new => "copyright (c) 2014" }, 20 | { :old => "Copyright © 1970", :new => "Copyright © 2014" }, 21 | { :old => "copyright © 1970", :new => "copyright © 2014" }, 22 | 23 | { :old => "(c) 1970", :new => "(c) 2014" }, 24 | { :old => "(C) 1970", :new => "(C) 2014" }, 25 | { :old => "© 1970", :new => "© 2014" }, 26 | 27 | ### 28 | 29 | { :old => "Copyright (C) 2010-2013", :new => "Copyright (C) 2010-2014" }, 30 | { :old => "Copyright (C) 2010-2012", :new => "Copyright (C) 2010-2012,2014" }, 31 | { :old => "Copyright (C) 2009,2012", :new => "Copyright (C) 2009,2012,2014" }, 32 | { :old => "Copyright (C) 2010,2013", :new => "Copyright (C) 2010,2013-2014" }, 33 | 34 | { :old => "Copyright (C) 2006,2008,2010,2012", :new => "Copyright (C) 2006,2008,2010,2012,2014" }, 35 | { :old => "Copyright (C) 2001,2003,2006,2008,2010,2012", :new => "Copyright (C) 2001,2003,2006,2008,2010,2012,2014" }, 36 | 37 | { :old => "Copyright (C) 1996-1998,2001-2002,2010-2013", :new => "Copyright (C) 1996-1998,2001-2002,2010-2014" }, 38 | { :old => "Copyright (C) 1996-1998,2001-2002,2010-2012", :new => "Copyright (C) 1996-1998,2001-2002,2010-2012,2014" }, 39 | { :old => "Copyright (C) 2001-2002,2010-2013", :new => "Copyright (C) 2001-2002,2010-2014" }, 40 | { :old => "Copyright (C) 2001,2003,2010-2013", :new => "Copyright (C) 2001,2003,2010-2014" }, 41 | 42 | { :old => "copyright © 1970 more words", :new => "copyright © 2014 more words" }, 43 | { :old => "words copyright © 1970", :new => "words copyright © 2014" }, 44 | 45 | ## 46 | 47 | { 48 | :old => "This is my paragraph. Blah blah blah... Copyright 2001.\nCopyright (C) 2001,2003,2006,2008,2010,2012", 49 | :new => "This is my paragraph. Blah blah blah... Copyright 2014.\nCopyright (C) 2001,2003,2006,2008,2010,2012,2014" 50 | }, 51 | { 52 | :old => "This is my paragraph. Blah blah blah... Copyright 2001.\nCopyright (C) 2001,2003,2006,2008,2010,2013", 53 | :new => "This is my paragraph. Blah blah blah... Copyright 2014.\nCopyright (C) 2001,2003,2006,2008,2010,2013-2014" 54 | }, 55 | { 56 | :old => "This is my paragraph. Blah blah blah... Copyright 2001.\nCopyright (C) 2001,2003,2006,2008,2010-2013", 57 | :new => "This is my paragraph. Blah blah blah... Copyright 2014.\nCopyright (C) 2001,2003,2006,2008,2010-2014" 58 | }, 59 | { 60 | :old => "This is my paragraph. Blah blah blah... Copyright 2001.\nCopyright (C) 2001,2003,2006,2008,2010-2013\n", 61 | :new => "This is my paragraph. Blah blah blah... Copyright 2014.\nCopyright (C) 2001,2003,2006,2008,2010-2014\n" 62 | }, 63 | 64 | # Case-insensitive-ness tests 65 | { :old => "COPYRIGHT (C) 2010", :new => "COPYRIGHT (C) 2014" }, 66 | { :old => "copyright (C) 2010", :new => "copyright (C) 2014" }, 67 | { :old => "CoPyRiGhT (C) 2010", :new => "CoPyRiGhT (C) 2014" }, 68 | { :old => "cOpYrIgHt (C) 2010", :new => "cOpYrIgHt (C) 2014" }, 69 | { :old => "COPYright (C) 2010", :new => "COPYright (C) 2014" }, 70 | { :old => "copyRIGHT (C) 2010", :new => "copyRIGHT (C) 2014" } 71 | ] 72 | 73 | garbage_text = [ 74 | "sjdfhaksjdfashdjfahskdjfh", 75 | "asdjfasdf", 76 | "hello these are words", 77 | "thing thing thing", 78 | "NOTCOPYRIGHT", 79 | "NOT COPYRIGHT", 80 | "(c) (c) (C)", 81 | "_dfasdf_", 82 | "© © ©", 83 | "REALLYLONGSTRINGWITHNOSPACES", 84 | "copyright © 1970more words" 85 | ] 86 | 87 | # ./lib/github-copywriter/regex.rb 88 | describe Copywriter do 89 | regex = Copywriter::Regex 90 | 91 | # Copywriter::Regex.update_copyright 92 | context "an out-of-date copyright..." do 93 | copyrights.each do |copyright| 94 | context(copyright[:old]) do 95 | updated_copyright = regex.update_copyright(2014, copyright[:old]) 96 | 97 | it { expect(updated_copyright[:content]) .to eq(copyright[:new]) } 98 | it { expect(updated_copyright[:copyrights_found]) .to be > 0 } 99 | it { expect(updated_copyright[:copyrights_updated]) .to be > 0 } 100 | end 101 | end 102 | end 103 | 104 | # Copywriter::Regex.update_copyright 105 | context "an up-to-date copyright..." do 106 | copyrights.each do |copyright| 107 | context(copyright[:new]) do 108 | updated_copyright = regex.update_copyright(2014, copyright[:new]) 109 | 110 | it { expect(updated_copyright[:content]) .to eq(copyright[:new]) } 111 | it { expect(updated_copyright[:copyrights_found]) .to be > 0 } 112 | it { expect(updated_copyright[:copyrights_updated]) .to be(0) } 113 | end 114 | end 115 | end 116 | 117 | # Copywriter::Regex.update_copyright 118 | context "garbage text input..." do 119 | garbage_text.each do |garbage| 120 | context(garbage) do 121 | updated_copyright = regex.update_copyright(2014, garbage) 122 | 123 | it { expect(updated_copyright[:content]) .to eq(garbage) } 124 | it { expect(updated_copyright[:copyrights_found]) .to be(0) } 125 | it { expect(updated_copyright[:copyrights_updated]) .to be(0) } 126 | end 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | require "github-copywriter" 3 | 4 | RSpec.configure do |config| 5 | # some (optional) config here 6 | end 7 | -------------------------------------------------------------------------------- /test_repo_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanmjacobs/github-copywriter/27fd9b4a2180c60eb0b56400fbbc84fb1a0e87e4/test_repo_demo.gif --------------------------------------------------------------------------------