├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── Manifest.txt ├── NOTES.md ├── README.md ├── Rakefile ├── bin ├── qk └── quik ├── lib ├── quik.rb └── quik │ ├── builder.rb │ ├── catalog.rb │ ├── cli │ ├── main.rb │ └── opts.rb │ ├── colors.rb │ ├── config.rb │ ├── merger.rb │ ├── package.rb │ ├── version.rb │ └── wizard.rb └── test ├── data └── gem-starter-template │ ├── .gitignore │ ├── Manifest.txt │ ├── README.md │ └── lib │ ├── $filename$.rb │ └── __filename__ │ ├── $filename$.rb │ ├── __filename__ │ └── test.rb │ └── version.rb ├── helper.rb ├── test_colors.rb ├── test_config.rb ├── test_merger.rb ├── test_package.rb └── test_wizard.rb /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore generated folders 2 | pkg/ 3 | doc/ 4 | 5 | ## temp folders 6 | tmp/ 7 | temp/ 8 | 9 | 10 | # ignore jekyll generated output 11 | site/_site/ 12 | 13 | ### 14 | # for testing downloading and unzipping packages ignore 15 | 16 | o/ 17 | *.zip 18 | gem.rb 19 | gemtest.rb 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.1 / 2015-08-17 2 | 3 | * Everything is new. First release 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | LICENSE.md 3 | Manifest.txt 4 | README.md 5 | Rakefile 6 | bin/qk 7 | bin/quik 8 | lib/quik.rb 9 | lib/quik/builder.rb 10 | lib/quik/catalog.rb 11 | lib/quik/cli/main.rb 12 | lib/quik/cli/opts.rb 13 | lib/quik/colors.rb 14 | lib/quik/config.rb 15 | lib/quik/merger.rb 16 | lib/quik/package.rb 17 | lib/quik/version.rb 18 | lib/quik/wizard.rb 19 | test/data/gem-starter-template/Manifest.txt 20 | test/data/gem-starter-template/README.md 21 | test/data/gem-starter-template/lib/$filename$.rb 22 | test/data/gem-starter-template/lib/__filename__/$filename$.rb 23 | test/data/gem-starter-template/lib/__filename__/__filename__/test.rb 24 | test/data/gem-starter-template/lib/__filename__/version.rb 25 | test/helper.rb 26 | test/test_colors.rb 27 | test/test_config.rb 28 | test/test_merger.rb 29 | test/test_package.rb 30 | test/test_wizard.rb 31 | -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | ## Todos 4 | 5 | - [ ] move todos over here :-) 6 | 7 | ## Quick Starter Gems / Libraries 8 | 9 | - - use ERB and Thor for template generation and scripting 10 | - 11 | 12 | 13 | 14 | ## Ideas 15 | 16 | - [ ] add remove .zip file after download and unpack 17 | 18 | by @StartZeroGnu (see https://github.com/quikstart/quik/issues/2) 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qk/quik - ruby quick starter template script wizard .:. the missing code generator 2 | 3 | * home :: [github.com/quikstart/quik](https://github.com/quikstart/quik) 4 | * bugs :: [github.com/quikstart/quik/issues](https://github.com/quikstart/quik/issues) 5 | * gem :: [rubygems.org/gems/quik](https://rubygems.org/gems/quik) 6 | * rdoc :: [rubydoc.info/gems/quik](http://rubydoc.info/gems/quik) 7 | 8 | 9 | ## Usage 10 | 11 | The quick gem includes a command line tool that lets you 12 | run quick starter template scripts. Try: 13 | 14 | ``` 15 | $ quik --help # or 16 | $ qk -h 17 | ``` 18 | 19 | Resulting in: 20 | 21 | ``` 22 | NAME 23 | qk/quik - ruby quick starter template script wizard .:. the missing code generator 24 | 25 | SYNOPSIS 26 | quik [global options] command [command options] [arguments...] 27 | 28 | VERSION 29 | 1.0.0 30 | 31 | GLOBAL OPTIONS 32 | --help - Show this message 33 | --test, --dry_run - (Debug) Dry run; run script in simulation for testing 34 | --verbose - (Debug) Show debug messages 35 | --version - Display the program version 36 | 37 | COMMANDS 38 | list, ls, l - List ruby quick starter scripts 39 | new, n - Run ruby quick starter script 40 | 41 | help - Shows a list of commands or help for one command 42 | test - (Debug) Test command suite 43 | ``` 44 | 45 | 46 | ### Commands 47 | 48 | [List Wizards](#list-wizard-command---list-ls-l) • 49 | [New Wizard](#new-wizard-command---new-n) 50 | 51 | 52 | #### List Wizards Command - `list`, `ls`, `l` 53 | 54 | Use: 55 | 56 | ``` 57 | $ quik list # or 58 | $ quik ls # or 59 | $ quik l # or 60 | $ qk l 61 | ``` 62 | 63 | Resulting in: 64 | 65 | ``` 66 | 1..gem .:. Gem Quick Starter Template 67 | 2..gem-hoe .:. Gem Quick Starter Template (Hoe Classic Edition) 68 | 3..sinatra .:. Sinatra Quick Starter Template 69 | ... 70 | ``` 71 | 72 | 73 | #### New Wizard Command - `new`, `n` 74 | 75 | To run a quick starter template wizard script 76 | to download and install (unzip/unpack) a template archive and configure 77 | the code ready-to-use. Try: 78 | 79 | 80 | ``` 81 | $ quik new gem # or 82 | $ quik n gem # or 83 | $ qk n gem 84 | ``` 85 | 86 | This will download the [`quik.rb`](https://github.com/quikstart/gem-starter-template/blob/master/quik.rb) wizard script from the 87 | gem starter template repo 88 | and run through all steps e.g.: 89 | 90 | ``` 91 | Welcome, to the gem quick starter script. 92 | 93 | Q: What's your gem's name? [hola]: hello 94 | Q: What's your gem's module? [Hola]: Hello 95 | 96 | Thanks! Ready-to-go. Stand back. 97 | 98 | Downloading Gem Starter Template... 99 | Setting up Gem Starter Template... 100 | ... 101 | Done. 102 | ``` 103 | 104 | That's it. Now the gem starter code is ready in the `hello` 105 | folder. 106 | 107 | 108 | **More Quick Starter Wizard Scripts** 109 | 110 | For more ruby quick starter scripts, see the [Quik Scripts](https://github.com/quikstart/scripts) 111 | catalog / directory. 112 | 113 | 114 | ## Install 115 | 116 | Just install the gem: 117 | 118 | $ gem install quik 119 | 120 | 121 | ## License 122 | 123 | The `quik` scripts are dedicated to the public domain. 124 | Use it as you please with no restrictions whatsoever. 125 | 126 | ## Questions? Comments? 127 | 128 | Send them along to the ruby-talk mailing list. 129 | Thanks! 130 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'hoe' 2 | require './lib/quik/version.rb' 3 | 4 | Hoe.spec 'quik' do 5 | 6 | self.version = Quik::VERSION 7 | 8 | self.summary = 'qk/quik - ruby quick starter template script wizard .:. the missing code generator' 9 | self.description = summary 10 | 11 | self.urls = { home: 'https://github.com/quikstart/quik' } 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 | ['logutils'], 22 | ['fetcher'], 23 | ['rubyzip'], 24 | ['gli'], 25 | ] 26 | 27 | self.licenses = ['Public Domain'] 28 | 29 | self.spec_extras = { 30 | required_ruby_version: '>= 2.2.2' 31 | } 32 | 33 | end 34 | -------------------------------------------------------------------------------- /bin/qk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'quik' 4 | 5 | Quik.main 6 | -------------------------------------------------------------------------------- /bin/quik: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'quik' 4 | 5 | Quik.main 6 | -------------------------------------------------------------------------------- /lib/quik.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # stdlibs 4 | require 'pp' 5 | require 'fileutils' 6 | require 'yaml' 7 | require 'json' 8 | require 'uri' 9 | require 'time' 10 | require 'date' 11 | 12 | 13 | # 3rd party libs/gems 14 | require 'fetcher' 15 | 16 | ## more 3rd party gems 17 | require 'zip' # use $ gem install rubyzip 18 | require 'gli' 19 | 20 | 21 | # our own code 22 | require 'quik/version' # let version always go first 23 | 24 | 25 | module Quik 26 | ### builtin default urls 27 | QUIK_SCRIPTS_URL = "https://github.com/quikstart/scripts/raw/master/scripts.yml" 28 | end 29 | 30 | 31 | 32 | require 'quik/catalog' 33 | require 'quik/package' 34 | require 'quik/merger' 35 | require 'quik/config' 36 | require 'quik/wizard' 37 | require 'quik/builder' 38 | require 'quik/colors' 39 | 40 | 41 | require 'quik/cli/opts' 42 | require 'quik/cli/main' 43 | 44 | 45 | module Quik 46 | def self.main( args=ARGV ) 47 | exit Tool.new.run( args ) 48 | end 49 | end # module Quik 50 | 51 | 52 | 53 | 54 | ## say hello 55 | puts Quik.banner if defined?($RUBYLIBS_DEBUG) && $RUBYLIBS_DEBUG 56 | -------------------------------------------------------------------------------- /lib/quik/builder.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | module Quik 4 | 5 | class Builder 6 | 7 | def self.load_file( path, opts={} ) 8 | code = File.open( path, 'r:utf-8' ) { |f| f.read } 9 | self.load( code, opts ) 10 | end 11 | 12 | def self.load( code, opts={} ) 13 | builder = Builder.new( opts ) 14 | builder.instance_eval( code ) 15 | builder 16 | end 17 | 18 | 19 | include Wizard ## mixin helpers for say, ask, yes?, no?, select, etc. 20 | 21 | def initialize( opts={} ) 22 | puts "==> starting new Quik script with options #{opts.inspect}; lets go" 23 | 24 | @test = opts[:dry_run] || opts[:test] || false 25 | @output_dir = opts[:o] || '.' 26 | @pak = nil ## for now reference last template pack (in future support many - why?? why not??) 27 | end 28 | 29 | ## "global" builder options 30 | def test?() @test; end ## dry_run option (defaults to false) 31 | def output_dir() @output_dir; end ## ouptput (root) dir (defaults to . e.g. working folder) 32 | 33 | 34 | 35 | def use( name, opts={} ) ## short alias for install_template 36 | install_template( name, opts ) 37 | end 38 | 39 | def install_template( name, opts= {} ) 40 | puts "==> handle install_template >#{name}< with options #{opts.inspect}" 41 | 42 | ## note for now assume name is key 43 | ## e.g. always downcase (e.g. Gem => gem) 44 | key = name.downcase 45 | 46 | if test? 47 | # do nothing; dry run 48 | else 49 | @pak = Package.new( key ) 50 | 51 | puts "GET #{@pak.remote_zip_url}".bold.green ## output network access in green bold 52 | @pak.download 53 | ## pak.unzip( "#{@output_dir}/#{key}" ) 54 | end 55 | end 56 | 57 | 58 | def config( opts={} ) 59 | puts "==> handle config block" 60 | c = OpenConfig.new 61 | yield( c ) 62 | ## pp c 63 | h = c.to_h 64 | 65 | pp h 66 | 67 | ## 68 | # check for name 69 | ## required for output 70 | ## if no name 71 | ## use ./ working folder for output!!! 72 | 73 | name = h['name'] 74 | 75 | if name.nil? || name.empty? 76 | fail 'sorry; for now name is required for output folder' 77 | end 78 | 79 | if test? 80 | ## do nothing; dry run 81 | else 82 | if @pak 83 | unzip_dir = "./#{name}" 84 | ## step 1: unzip templates 85 | @pak.unzip( unzip_dir ) 86 | ## step 2: merge templates (using hash/config settings) 87 | m = Quik::Merger.new 88 | m.merge( unzip_dir, h ) 89 | end 90 | end 91 | end # method config 92 | 93 | 94 | end # class Builder 95 | 96 | end # module Quik 97 | -------------------------------------------------------------------------------- /lib/quik/catalog.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Quik 4 | 5 | class Catalog 6 | 7 | def self.from_url( src ) 8 | worker = Fetcher::Worker.new 9 | text = worker.read_utf8!( src ) 10 | new( text ) 11 | end 12 | 13 | def self.from_file( path ) 14 | ## read in themes catalog 15 | text = File.open( path, 'r:utf-8' ) { |f| f.read } 16 | new( text ) 17 | end 18 | 19 | # def self.from_string( text ) 20 | # self.new( text ) 21 | # end 22 | 23 | 24 | def initialize( text ) 25 | recs = YAML.load( text ) 26 | ## note: convert array to hash (lookup by name) 27 | ## todo: check for missing name property? - why? why not? 28 | @scripts = recs.reduce( {} ) { |h,rec| h[rec['name']] = rec; h } 29 | end 30 | 31 | 32 | def find( name ) 33 | @scripts[ name ] 34 | end 35 | 36 | def list( filter=nil ) 37 | ## pp filter 38 | 39 | @scripts.each_with_index do |(_,rec),i| 40 | name = rec['name'] 41 | summary = rec['summary'] 42 | script = rec['script'] 43 | 44 | if script && script.index( '//github.com/') 45 | ## "beautify" / shorten github links - remove raw/master/ 46 | script = script.gsub('raw/master/', '' ) 47 | end 48 | 49 | ## pp h 50 | 51 | line = '' 52 | line << " %3d" % (i+1) 53 | line << "..%-10s" % name 54 | line << " .:. #{summary}" 55 | line << " @ (#{script})" 56 | 57 | if filter 58 | ## note: ignore case (upper/lower/downcase) for now 59 | ## filter on name/title and 60 | ## tags for now 61 | if name.downcase.index(filter.downcase) != nil || ## note: 0 is match found on index 0; only nil is not found 62 | summary.downcase.index(filter.downcase) != nil 63 | puts line 64 | end 65 | else 66 | puts line 67 | end 68 | end 69 | end # method filter 70 | 71 | end # class Catalog 72 | 73 | end # module Quik 74 | -------------------------------------------------------------------------------- /lib/quik/cli/main.rb: -------------------------------------------------------------------------------- 1 | ################ 2 | ## NOTE: wrap gli config into a class 3 | ## see github.com/davetron5000/gli/issues/153 4 | 5 | module Quik 6 | 7 | class Tool 8 | def initialize 9 | LogUtils::Logger.root.level = :info # set logging level to info 10 | end 11 | 12 | def run( args ) 13 | puts Quik.banner 14 | Toolii.run( args ) 15 | end 16 | end 17 | 18 | 19 | ## NOTE: gli added function are class methods (thus, wrap class Toolii in Tool for now) 20 | 21 | class Toolii 22 | extend GLI::App 23 | 24 | def self.logger=(value) @@logger=value; end 25 | def self.logger() @@logger; end 26 | 27 | ## todo: find a better name e.g. change to settings? config? safe_opts? why? why not? 28 | def self.opts=(value) @@opts = value; end 29 | def self.opts() @@opts; end 30 | 31 | 32 | logger = LogUtils::Logger.root 33 | opts = Opts.new 34 | 35 | 36 | program_desc 'ruby quick starter template script wizard .:. the missing code generator' 37 | version VERSION 38 | 39 | 40 | ## desc 'Use only local (offline) cached data; no (online) remote network access' 41 | ## switch [:l, :local], negatable: false 42 | 43 | desc '(Debug) Show debug messages' 44 | switch [:verbose], negatable: false ## todo: use -w for short form? check ruby interpreter if in use too? 45 | 46 | desc '(Debug) Dry run; run script in simulation for testing' 47 | switch [:test, :dry_run], negatable: false 48 | 49 | 50 | 51 | def self.fetch_catalog 52 | ## scripts_dir = "#{Quik.root}/test/data" 53 | ## catalog = Catalog.new( "#{scripts_dir}/scripts.yml" ) 54 | 55 | url = QUIK_SCRIPTS_URL 56 | 57 | puts "GET #{url}".bold.green ## output network access in green bold 58 | 59 | catalog = Catalog.from_url( url ) 60 | catalog 61 | end 62 | 63 | 64 | def self.fetch_script( name_or_path ) 65 | 66 | ## first try local version in working folder 67 | 68 | text = '' 69 | 70 | extname = File.extname( name_or_path ) 71 | 72 | 73 | ## try local first 74 | ## todo/fix: check in ~/.quik/ dir too? 75 | 76 | path = if extname == '.rb' && File.exist?( name_or_path ) 77 | name_or_path 78 | elsif extname == '' && File.exist?( "#{name_or_path}.rb" ) 79 | "#{name_or_path}.rb" 80 | elsif extname == '' && File.exist?( "~/.quik/#{name_or_path}.rb" ) 81 | "~/.quik/#{name_or_path}.rb" ## todo/check - if (~) shortcut works in windows -too? 82 | else 83 | nil 84 | end 85 | 86 | if path 87 | text = File.open( path, 'r:utf-8' ) { |f| f.read } 88 | else ## try fetch remote script 89 | 90 | url = nil 91 | 92 | if name_or_path.index( '/' ).nil? ## assume short-cut name if no (/) in name 93 | ## do a lookup from catalog!!! 94 | catalog = fetch_catalog 95 | rec = catalog.find( name_or_path ) 96 | url = rec['script'] if rec 97 | 98 | if url.nil? 99 | puts "!! ERROR - sorry no wizard script found / available for name >#{name_or_path}<" 100 | exit 1 101 | end 102 | else 103 | ## assume custom github url shortcut 104 | url = "https://github.com/#{name_or_path}/raw/master/quik.rb" 105 | end 106 | 107 | ## assume utf8 text encoding for now 108 | puts "GET #{url}".bold.green ## output network access in green bold 109 | 110 | worker = Fetcher::Worker.new 111 | text = worker.read_utf8!( url ) 112 | end 113 | 114 | text 115 | end 116 | 117 | 118 | 119 | desc "List ruby quick starter scripts" 120 | arg_name 'QUERY' # optional search query/filter 121 | command [:list,:ls,:l] do |c| 122 | 123 | c.action do |g,o,args| 124 | ## read in scripts diretory 125 | catalog = fetch_catalog 126 | catalog.list( args[0] ) ## note: pass in filter e.g. args[0]; may be nil (no filter) 127 | puts 'Done.' 128 | end # action 129 | end # command list 130 | 131 | 132 | desc "Run ruby quick starter script" 133 | arg_name 'NAME' # required theme name 134 | command [:new,:n] do |c| 135 | 136 | c.action do |g,o,args| 137 | 138 | name = args[0] || 'gem' 139 | 140 | script = fetch_script( name ) 141 | if opts.test? 142 | puts "dry (test) run:" 143 | Builder.load( script, test: true ) 144 | else 145 | Builder.load( script ) 146 | end 147 | 148 | puts 'Done.' 149 | end # action 150 | end # command setup 151 | 152 | 153 | 154 | desc '(Debug) Test command suite' 155 | command :test do |c| 156 | c.action do |g,o,args| 157 | 158 | puts "hello from test command" 159 | puts "args (#{args.class.name}):" 160 | pp args 161 | puts "o (#{o.class.name}):" 162 | pp o 163 | puts "g (#{g.class.name}):" 164 | pp g 165 | 166 | LogUtils::Logger.root.debug 'test debug msg' 167 | LogUtils::Logger.root.info 'test info msg' 168 | LogUtils::Logger.root.warn 'test warn msg' 169 | 170 | puts 'Done.' 171 | end 172 | end 173 | 174 | 175 | 176 | pre do |g,c,o,args| 177 | opts.merge_gli_options!( g ) 178 | opts.merge_gli_options!( o ) 179 | 180 | puts Quik.banner 181 | 182 | if opts.verbose? 183 | LogUtils::Logger.root.level = :debug 184 | end 185 | 186 | logger.debug "Executing #{c.name}" 187 | true 188 | end 189 | 190 | post do |global,c,o,args| 191 | logger.debug "Executed #{c.name}" 192 | true 193 | end 194 | 195 | 196 | on_error do |e| 197 | puts 198 | puts "*** error: #{e.message}" 199 | 200 | if opts.verbose? 201 | puts e.backtrace 202 | end 203 | 204 | false # skip default error handling 205 | end 206 | 207 | 208 | ### exit run(ARGV) ## note: use Toolii.run( ARGV ) outside of class 209 | 210 | end # class Toolii 211 | 212 | end # module Quik 213 | 214 | -------------------------------------------------------------------------------- /lib/quik/cli/opts.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Quik 4 | 5 | class Opts 6 | 7 | def merge_gli_options!( options = {} ) 8 | @test = true if options[:test] == true 9 | @verbose = true if options[:verbose] == true 10 | end 11 | 12 | 13 | def verbose=(boolean) # add: alias for debug ?? 14 | @verbose = boolean 15 | end 16 | 17 | def verbose? 18 | return false if @verbose.nil? # default verbose/debug flag is false 19 | @verbose == true 20 | end 21 | 22 | def test=(boolean) 23 | @test = boolean 24 | end 25 | 26 | def test? 27 | return false if @test.nil? # default test/dry-run flag is false 28 | @test == true 29 | end 30 | 31 | end # class Opts 32 | 33 | end # module Quik 34 | -------------------------------------------------------------------------------- /lib/quik/colors.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ### todo: move to textutils for (re)use - why? why not?? 4 | ### todo add some references to alternative color gems 5 | ## 6 | ## e.g. https://github.com/fazibear/colorize 7 | ## http://github.com/ssoroka/ansi 8 | ## http://flori.github.com/term-ansicolor/ 9 | ## http://github.com/sickill/rainbow 10 | ## https://github.com/janlelis/paint 11 | 12 | 13 | ## move to core_ext.rb - why? why not?? 14 | 15 | ### 16 | ## fix: use css style 17 | ## e.g. color( :red ) 18 | ## color( :blue ) 19 | ## background_color( :red ) 20 | ## font_style( :bold ) -- why? why not?? 21 | 22 | class String 23 | 24 | def self.use_colors? 25 | @@use_color ||= true 26 | ## todo/fix: check for windows on load (set to false;otherwise true) 27 | @@use_color 28 | end 29 | 30 | def self.use_colors=(value) 31 | @@use_color = value 32 | end 33 | 34 | 35 | def red() colorize(31); end 36 | def green() colorize(32); end 37 | def yellow() colorize(33); end ## note: more brown-ish (if not used w/ bold/bright mode) ?? 38 | def blue() colorize(34); end 39 | def magenta() colorize(35); end ## pink ?? 40 | def cyan() colorize(36); end ## light blue ?? 41 | 42 | def bold() colorize(1); end ## just use 0 clear for now; instead of BOLD_OFF (22) code; use bright as an alias?? 43 | 44 | private 45 | def colorize(code) 46 | if self.class.use_colors? 47 | "\e[#{code}m#{self}\e[0m" 48 | else 49 | self 50 | end 51 | end 52 | 53 | 54 | ## todo - add modes e.g. bold/underscore/etc. 55 | =begin 56 | class String 57 | def black; "\e[30m#{self}\e[0m" end 58 | def red; "\e[31m#{self}\e[0m" end 59 | def green; "\e[32m#{self}\e[0m" end 60 | def brown; "\e[33m#{self}\e[0m" end 61 | def blue; "\e[34m#{self}\e[0m" end 62 | def magenta; "\e[35m#{self}\e[0m" end 63 | def cyan; "\e[36m#{self}\e[0m" end 64 | def gray; "\e[37m#{self}\e[0m" end 65 | 66 | def on_black; "\e[40m#{self}\e[0m" end 67 | def on_red; "\e[41m#{self}\e[0m" end 68 | def on_green; "\e[42m#{self}\e[0m" end 69 | def on_brown; "\e[43m#{self}\e[0m" end 70 | def on_blue; "\e[44m#{self}\e[0m" end 71 | def on_magenta; "\e[45m#{self}\e[0m" end 72 | def on_cyan; "\e[46m#{self}\e[0m" end 73 | def on_gray; "\e[47m#{self}\e[0m" end 74 | 75 | def bold; "\e[1m#{self}\e[21m" end 76 | def italic; "\e[3m#{self}\e[23m" end 77 | def underline; "\e[4m#{self}\e[24m" end 78 | def blink; "\e[5m#{self}\e[25m" end 79 | def reverse_color; "\e[7m#{self}\e[27m" end 80 | 81 | use Ansi constants - why? why not? e.g.: 82 | //foreground color 83 | public static final String BLACK_TEXT() { return "\033[30m";} 84 | public static final String RED_TEXT() { return "\033[31m";} 85 | public static final String GREEN_TEXT() { return "\033[32m";} 86 | public static final String BROWN_TEXT() { return "\033[33m";} 87 | public static final String BLUE_TEXT() { return "\033[34m";} 88 | public static final String MAGENTA_TEXT() { return "\033[35m";} 89 | public static final String CYAN_TEXT() { return "\033[36m";} 90 | public static final String GRAY_TEXT() { return "\033[37m";} 91 | 92 | //background color 93 | public static final String BLACK_BACK() { return "\033[40m";} 94 | public static final String RED_BACK() { return "\033[41m";} 95 | public static final String GREEN_BACK() { return "\033[42m";} 96 | public static final String BROWN_BACK() { return "\033[43m";} 97 | public static final String BLUE_BACK() { return "\033[44m";} 98 | public static final String MAGENTA_BACK() { return "\033[45m";} 99 | public static final String CYAN_BACK() { return "\033[46m";} 100 | public static final String WHITE_BACK() { return "\033[47m";} 101 | 102 | //ANSI control chars 103 | public static final String RESET_COLORS() { return "\033[0m";} 104 | public static final String BOLD_ON() { return "\033[1m";} 105 | public static final String BLINK_ON() { return "\033[5m";} 106 | public static final String REVERSE_ON() { return "\033[7m";} 107 | public static final String BOLD_OFF() { return "\033[22m";} 108 | public static final String BLINK_OFF() { return "\033[25m";} 109 | public static final String REVERSE_OFF() { return "\033[27m";} 110 | end 111 | 112 | Code Effect 113 | 0 Turn off all attributes 114 | 1 Set bright mode 115 | 4 Set underline mode 116 | 5 Set blink mode 117 | 7 Exchange foreground and background colors 118 | 8 Hide text (foreground color would be the same as background) 119 | 30 Black text 120 | 31 Red text 121 | 32 Green text 122 | 33 Yellow text 123 | 34 Blue text 124 | 35 Magenta text 125 | 36 Cyan text 126 | 37 White text 127 | 39 Default text color 128 | 40 Black background 129 | 41 Red background 130 | 42 Green background 131 | 43 Yellow background 132 | 44 Blue background 133 | 45 Magenta background 134 | 46 Cyan background 135 | 47 White background 136 | 49 Default background color 137 | 138 | note: 139 | puts "\e[31m" # set format (red foreground) 140 | puts "\e[0" # clear format 141 | puts "green-#{"red".red}-green".green # will be green-red-normal, because of \e[0 142 | e.g. for now colors can NOT get nested 143 | plus if you bold/italic/etc. use it before the color e.g. 144 | bold.red etc. 145 | =end 146 | 147 | 148 | end # class String 149 | -------------------------------------------------------------------------------- /lib/quik/config.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | module Quik 4 | 5 | ## 6 | # used for config block 7 | # lets you access props (even nested) that don't yet exist 8 | # and all props get stored in a hash 9 | # 10 | # e.g 11 | # c = OpenConfig.new 12 | # c.title = 'title' 13 | # c.author.name = 'name' 14 | 15 | # c.quik.last_updated = Time.now 16 | # c.quik.title = 'title' 17 | # c.quik.name = 'name' 18 | # c.quik.theme = 'theme' 19 | 20 | class OpenConfig 21 | 22 | def initialize 23 | @h = {} 24 | end 25 | 26 | def to_h 27 | h = {} 28 | @h.each do |k,v| 29 | if v.is_a? OpenConfig 30 | h[ k ] = v.to_h 31 | else 32 | h[ k ] = v ## just pass along as is 33 | end 34 | end 35 | h 36 | end 37 | 38 | def method_missing( m, *args, &block) 39 | if m.to_s =~ /^(.*)=$/ ## setter 40 | puts "config lookup (setter) >#{m}< #{m.class.name}, #{args.inspect}" 41 | key = m[0..-2].to_s ## cut off trailing = 42 | @h[ key ] = args[0].to_s # note: assume first arg is value for setter 43 | # note: for now all values are strings (always use to_s) 44 | else ## assume getter 45 | ## fix: add check for args?? must be 0 for getters?? 46 | ## use else super to delegate non-getters?? 47 | puts "config lookup (getter) >#{m}< #{m.class.name}" 48 | key = m.to_s 49 | value = @h[ key ] 50 | if value.nil? 51 | puts " config add (nested) hash" 52 | value = @h[ key ] = OpenConfig.new 53 | end 54 | value 55 | end 56 | end # method_missing 57 | 58 | end # class OpenConfig 59 | 60 | end # module Quik 61 | -------------------------------------------------------------------------------- /lib/quik/merger.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Quik 4 | 5 | class Merger 6 | 7 | 8 | 9 | def find_files( root_dir ) 10 | tree = [] 11 | 12 | files = Dir.entries( root_dir ) 13 | files = files.sort 14 | puts "#{root_dir}:" 15 | pp files 16 | 17 | files.each do |file| 18 | if File.directory?( "#{root_dir}/#{file}" ) 19 | ## note: skip directory if it starts with dot e.g. . or .. or .git etc. 20 | if file.start_with?( '.' ) 21 | puts "skipping directory >#{file}< (#{root_dir})" 22 | next 23 | end 24 | subtree = find_files( "#{root_dir}/#{file}" ) 25 | tree << [ file, subtree ] 26 | else 27 | ## just a "regular" file 28 | tree << file 29 | end 30 | end 31 | tree 32 | end 33 | 34 | 35 | def merge_filenames( root_dir, hash, opts={} ) ## flags e.g. noop, verbose, etc. see FileUtils 36 | tree = find_files( root_dir ) 37 | pp tree 38 | 39 | flags = { ## always use verbose mode for now 40 | verbose: true 41 | } 42 | 43 | ## todo/check: move opts={} to initialize e.g. Merger.new( opts={}) why? why not?? 44 | 45 | ## check for noop e.g. test mode/dry run 46 | flags[ :noop ] = true if opts[:test] || opts[:dry_run] || opts[:noop] 47 | 48 | puts "walk tree:" 49 | merge_filenames_worker_step1( root_dir, hash, tree, flags ) 50 | merge_filenames_worker_step2( root_dir, hash, tree, flags ) 51 | end 52 | 53 | def merge_filenames_worker_step1( root_dir, hash, tree, flags ) 54 | ## note: step 1 move all files 55 | ## step 2 move all dirs 56 | 57 | tree.each do |node| 58 | if node.is_a? Array ## assume it's a directory 59 | merge_filenames_worker_step1( "#{root_dir}/#{node[0]}", hash, node[1], flags ) 60 | else ## assume it's a file 61 | old_name = node 62 | new_name = merge_path( old_name, hash ) 63 | if old_name == new_name 64 | puts " keep file #{node} (#{root_dir})" 65 | else 66 | puts " *** move file #{old_name} => #{new_name} (#{root_dir})" 67 | src_path = "#{root_dir}/#{old_name}" 68 | dest_path = "#{root_dir}/#{new_name}" 69 | ## note: make sure subpath exists (e.g. replacement might include new (sub)dirs too) 70 | FileUtils.mkdir_p( File.dirname( dest_path ), flags ) unless Dir.exist?( File.dirname( dest_path )) 71 | FileUtils.mv( src_path, dest_path, flags ) 72 | end 73 | end 74 | end 75 | end 76 | 77 | def merge_filenames_worker_step2( root_dir, hash, tree, flags ) 78 | ## note: step 1 move all files 79 | ## step 2 move all dirs 80 | 81 | tree.each do |node| 82 | if node.is_a? Array ## assume it's a directory 83 | merge_filenames_worker_step2( "#{root_dir}/#{node[0]}", hash, node[1], flags ) 84 | old_name = node[0] 85 | new_name = merge_path( old_name, hash ) 86 | if old_name == new_name 87 | puts " keep dir #{node[0]} (#{root_dir})" 88 | else 89 | puts " *** move dir #{old_name} => #{new_name} (#{root_dir})" 90 | src_path = "#{root_dir}/#{old_name}" 91 | dest_path = "#{root_dir}/#{new_name}" 92 | ## note: make sure subpath exists (e.g. replacement might include new (sub)dirs too) 93 | FileUtils.mkdir_p( File.dirname( dest_path ), flags ) unless Dir.exist?( File.dirname( dest_path )) 94 | FileUtils.mv( src_path, dest_path, flags ) 95 | end 96 | end 97 | end 98 | end 99 | 100 | 101 | 102 | def merge_files( root_dir, hash, opts={} ) 103 | ## note: rescan files (after renames) 104 | tree = find_files( root_dir ) 105 | pp tree 106 | 107 | puts "walk tree:" 108 | merge_files_worker( root_dir, '', hash, tree, opts ) 109 | end 110 | 111 | def merge_files_worker( root_dir, relative_dir, hash, tree, opts ) 112 | tree.each do |node| 113 | if node.is_a? Array ## assume it's a directory 114 | if relative_dir.empty? # e.g. just add w/o leading slash e.g. 'lib' and not '/lib' 115 | new_relative_dir = node[0].dup # note: create a new string; just in case 116 | else 117 | new_relative_dir = "#{relative_dir}/#{node[0]}" 118 | end 119 | merge_files_worker( root_dir, new_relative_dir, hash, node[1], opts ) 120 | else ## assume it's a file 121 | if relative_dir.empty? 122 | relative_path = node 123 | else 124 | relative_path = "#{relative_dir}/#{node}" 125 | end 126 | 127 | src_path = "#{root_dir}/#{relative_path}" 128 | 129 | ## note: for now assume always assume text files/utf8 130 | old_text = File.open( src_path, 'r:utf-8' ) { |f| f.read } 131 | new_text = merge_text( old_text, hash ) 132 | 133 | if opts[:o] 134 | dest_root = opts[:o] 135 | dest_path = "#{dest_root}/#{relative_path}" 136 | ## make sure dest_path exists 137 | dest_dir = File.dirname( dest_path ) 138 | FileUtils.mkdir_p( dest_dir ) unless Dir.exist?( dest_dir ) 139 | else 140 | dest_root = root_dir 141 | dest_path = "#{dest_root}/#{relative_path}" 142 | end 143 | 144 | if old_text == new_text 145 | if opts[:o] ## for testing copy file 1:1 146 | puts " copy file 1:1 #{node} (#{relative_dir}) in (#{dest_root})" 147 | FileUtils.cp( src_path, dest_path, verbose: true ) 148 | else 149 | puts " skip file #{node} (#{relative_dir})" 150 | end 151 | else 152 | puts " *** update file #{node} (#{relative_dir}) in (#{dest_root})" 153 | File.open( dest_path, 'w:utf-8' ) do |f| 154 | f.write( new_text ) 155 | end 156 | end 157 | end 158 | end 159 | end 160 | 161 | 162 | 163 | def merge_path( path, hash ) 164 | ## e.g. allow 165 | ## __filename__ or 166 | ## $filename$ for now 167 | ## note: allow underline too e.g $file_name$ etc. 168 | path.gsub( /(__|\$)([a-z_]+)\1/i ) do |_| 169 | key = $2.to_s 170 | value = hash[ key ] 171 | puts " [path] replacing #{key} w/ >#{value}< in (#{path})" 172 | value 173 | end 174 | end 175 | 176 | def merge_text( text, hash ) 177 | ## e.g. allow 178 | ## $filename$ for now only in text 179 | ## note: must include leading and trailing word boundry (/B) 180 | ## e.g. hello$test$ will not match only "free-standing $test 181 | ## or in quote e.g. "$test$" 182 | ## e.g. no letters or digits allowed before or after $ to match 183 | ## note: allow underline too e.g. $test_klass$ etc. 184 | 185 | ## pp text 186 | 187 | text.gsub( /\B\$([a-z_]+)\$\B/i ) do |_| 188 | key = $1.to_s 189 | value = hash[ key ] 190 | puts " [text] replacing #{key} w/ >#{value}<" 191 | value 192 | end 193 | end 194 | 195 | 196 | 197 | def merge( root_dir, hash, opts={} ) 198 | puts " merge #{root_dir}, #{hash.inspect}" 199 | 200 | merge_filenames( root_dir, hash, opts ) 201 | merge_files( root_dir, hash, opts ) 202 | end 203 | 204 | end # class Merger 205 | 206 | end # module Quik 207 | 208 | -------------------------------------------------------------------------------- /lib/quik/package.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Quik 4 | 5 | class Package 6 | 7 | def initialize( key ) ## e.g. quikstart/gems 8 | if key.index( '/' ).nil? ## if just 'gems' etc; assumbe quikstart "standard" github org 9 | @key = "quikstart/#{key}" 10 | else 11 | @key = key # use as is e.g. seattlerb/hoe etc. 12 | end 13 | end 14 | 15 | 16 | def remote_zip_url # remote zip url 17 | "https://github.com/#{@key}/archive/master.zip" 18 | end 19 | 20 | def local_zip_name 21 | ## note: change / to --I-- for "flat" structure (e.g. no dirs) 22 | @key.sub( '/', '--I--' ) # note: will NOT include/return .zip extension 23 | end 24 | 25 | def local_zip_dir 26 | "." ## use ./tmp or ./dl or ~/.quik/cache - why?? why not?? 27 | end 28 | 29 | def local_zip_path # local zip path 30 | "#{local_zip_dir}/#{local_zip_name}.zip" 31 | end 32 | 33 | 34 | 35 | def download 36 | src = remote_zip_url 37 | dest_zip = local_zip_path 38 | 39 | ## make sure dest folder exists 40 | FileUtils.mkdir_p( local_zip_dir ) unless Dir.exist?( local_zip_dir ) 41 | fetch_archive( src, dest_zip ) 42 | end 43 | 44 | def unzip( unzip_dir ) 45 | src = local_zip_path 46 | dest_unzip = unzip_dir ## local_unzip_dir 47 | 48 | ## check if folders exists? if not create folder in path 49 | FileUtils.mkdir_p( dest_unzip ) unless Dir.exist?( dest_unzip ) 50 | unzip_archive( src, dest_unzip ) 51 | end 52 | 53 | 54 | def merge( root_dir, hash ) 55 | ## replace/merge variable in files 56 | Dir.chdir( "#{root_dir}" ) do 57 | files = Dir[ '**/*' ] ## get all files 58 | pp files 59 | end 60 | end 61 | 62 | 63 | private 64 | def fetch_archive( src, dest ) 65 | ## step 1 - fetch archive 66 | worker = Fetcher::Worker.new 67 | worker.copy( src, dest ) 68 | ### fix: add src.sha5 69 | ### inside folder 70 | ### lets us check if current HEAD version is in place across datafiles etc. 71 | ## - try HTTP HEAD ?? to check? 72 | end 73 | 74 | def unzip_archive( src, dest, opts={} ) 75 | ### todo/fix: rename or remove root folder -- use opts { root: false or something??} 76 | # e.g 77 | # !/starter-gh-pages/_layouts/ becomes 78 | # !/_layouts/ etc. 79 | 80 | ## 81 | ## note: 82 | ## skip all files in "root" folder 83 | ## only unzip files in /template(s)/ folder 84 | 85 | Zip::File.open( src ) do |zipfile| 86 | zipfile.each do |file| 87 | if file.directory? 88 | puts " skip directory zip entry - #{file.name}" 89 | else 90 | ### fix: only cut-off if master or gh-pages ??? 91 | ## check if others include root folder? 92 | name = file.name[ file.name.index('/')+1..-1] ## cut-off root/first path entry 93 | 94 | ## note: name must start w/ template/ or templates/ 95 | ## otherwise gets skipped as "top level" docu 96 | if name =~ /^template(s)?\// 97 | name = name[ name.index('/')+1..-1] ## cut-off first path entry (e.g. template(s)/) 98 | 99 | path = File.join( dest, name) 100 | puts " unzip file zip entry - #{file.name} to #{path}" 101 | FileUtils.mkdir_p( File.dirname( path) ) 102 | ## todo/fix: check - always overwrite if file exists - why? why not?? 103 | zipfile.extract(file, path) unless File.exist?(path) 104 | else 105 | puts " skip top-level docu file entry - #{file.name}" 106 | end 107 | end 108 | end 109 | end 110 | end # method unzip_theme 111 | 112 | 113 | end # class Package 114 | 115 | end # module Quik 116 | 117 | -------------------------------------------------------------------------------- /lib/quik/version.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Quik 4 | 5 | MAJOR = 1 ## todo: namespace inside version or something - why? why not?? 6 | MINOR = 0 7 | PATCH = 0 8 | VERSION = [MAJOR,MINOR,PATCH].join('.') 9 | 10 | def self.version 11 | VERSION 12 | end 13 | 14 | # version string for generator meta tag (includes ruby version) 15 | def self.banner 16 | "quik/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}] in (#{root})" 17 | end 18 | 19 | def self.root 20 | File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) ) 21 | end 22 | 23 | end # module Quik 24 | 25 | -------------------------------------------------------------------------------- /lib/quik/wizard.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | 4 | ## note: 5 | ## use global $QUIK_WIZARD_STDIN 6 | ## lets you redirect stdin for testing e.g $QUIK_WIZARD_STDIN = StringIO.new( 'test/n' ) 7 | $QUIK_WIZARD_IN = $stdin 8 | 9 | 10 | module Quik 11 | 12 | module Wizard ## use a different name e.g. WizardHelpers, FormHelpers, InputHelper, etc - why, why not?? 13 | 14 | def getstr ## use getstr to avoid conflict w/ gets (use better name? read_string, readline (already exists too), etc.?) 15 | ## note: gets will include trailing newline (user hits return to enter data) 16 | ## - use strip for now (remove leading and traling whitspaces) - might later just use chomp ? (jsut removes newlines) 17 | $QUIK_WIZARD_IN.gets.strip 18 | end 19 | 20 | def say( text ) 21 | puts text 22 | end 23 | 24 | 25 | YES_REGEX = /y|yes|on|t|true/i ## support YAML true values - double check (YAML does NOT support t/f) 26 | NO_REGEX = /n|no|off|f|false/i 27 | 28 | def yes?( question ) ## defaults to yes - why, why not?? 29 | ## todo: strip trailing question mark (?) if present (gets auto-included) 30 | print( "Q: #{question} (y/n)? [y]: " ) 31 | str = getstr 32 | if str.empty? || str =~ YES_REGEX 33 | true 34 | elsif str =~ NO_REGEX 35 | false 36 | else ## warn: unknown value?? 37 | true 38 | end 39 | end 40 | 41 | def no?( question ) ## defaults to yes - why, why not?? 42 | ## todo: strip trailing question mark (?) if present (gets auto-included) 43 | print( "Q: #{question} (y/n)? [n]: " ) 44 | str = getstr 45 | if str.empty? || str =~ NO_REGEX 46 | true 47 | elsif str =~ YES_REGEX 48 | false 49 | else ## warn: unknown value?? 50 | true 51 | end 52 | end 53 | 54 | 55 | def ask( question, default=nil ) 56 | ## todo: strip trailing question mark (?) if present (gets auto-included) 57 | if default 58 | print( "Q: #{question}? [#{default}]: " ) 59 | else 60 | print( "Q: #{question}?: " ) 61 | end 62 | 63 | str = getstr 64 | if default && str.empty? ## todo: best way to check for empty string? 65 | str = default 66 | end 67 | str 68 | end 69 | 70 | 71 | def select( title, options ) 72 | puts( "Q: #{title}: " ) 73 | options.each_with_index do |opt,i| 74 | puts " #{i+1} - #{opt}" 75 | end 76 | print( " Your choice (1-#{options.size})? [1]: " ) 77 | str = getstr 78 | if str.empty? ## todo: best way to check for empty string? 79 | num = 0 ## default to first option for now 80 | else 81 | num = str.to_i ## note: defaults to 0 if cannot convert? 82 | num -= 1 if num > 0 ## note: "convert" from 1-based to 0-based for ary; if invalid entry; default to 0 83 | end 84 | options[ num ] 85 | end 86 | 87 | end # module Wizard 88 | end # module Quik 89 | -------------------------------------------------------------------------------- /test/data/gem-starter-template/.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 | -------------------------------------------------------------------------------- /test/data/gem-starter-template/Manifest.txt: -------------------------------------------------------------------------------- 1 | --- 2 | # Gem Starter Template 3 | --- 4 | Manifest.txt 5 | HISTORY.md 6 | README.md 7 | Rakefile 8 | lib/{{filename}}.rb 9 | lib/{{filename}}/version.rb 10 | -------------------------------------------------------------------------------- /test/data/gem-starter-template/README.md: -------------------------------------------------------------------------------- 1 | # gem-starter-template 2 | 3 | gem starter template 4 | 5 | 6 | ## Settings / Config 7 | 8 | ~~~ 9 | {{ filename }} used for lib/{{ filename }}.rb 10 | {{ project }} used for gem name 11 | {{ klass }} used for class name (use class - why? why not?) 12 | ~~~ 13 | 14 | filename encode with --filename-- for variables e.g. lib/--filename--.rb -- why? why not? 15 | 16 | 17 | use three jekyll conventions: 18 | 19 | - folders w/ underscore get ignored 20 | - files w/ front matter e.g. ---...--- get (pre)processed 21 | - files w/o front matter get copied 1:1 22 | 23 | -------------------------------------------------------------------------------- /test/data/gem-starter-template/lib/$filename$.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quikstart/quik/b041fd23d670499010bc07f29254d50272431df0/test/data/gem-starter-template/lib/$filename$.rb -------------------------------------------------------------------------------- /test/data/gem-starter-template/lib/__filename__/$filename$.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quikstart/quik/b041fd23d670499010bc07f29254d50272431df0/test/data/gem-starter-template/lib/__filename__/$filename$.rb -------------------------------------------------------------------------------- /test/data/gem-starter-template/lib/__filename__/__filename__/test.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quikstart/quik/b041fd23d670499010bc07f29254d50272431df0/test/data/gem-starter-template/lib/__filename__/__filename__/test.rb -------------------------------------------------------------------------------- /test/data/gem-starter-template/lib/__filename__/version.rb: -------------------------------------------------------------------------------- 1 | 2 | 3 | module $klass$ 4 | VERSION = $version$ 5 | VERSION2 = "$version$" 6 | VERSION3 = '$version$' 7 | VERSION4 = $test 8 | VERSION5 = test$test$hello 9 | VERSION6 = test$test$ 10 | VERSION7 = $$ 11 | VERSION8 = $1$2 12 | end 13 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | 3 | 4 | class EchoIO 5 | def initialize( buf ) 6 | @io = StringIO.new( buf ) 7 | end 8 | 9 | def gets 10 | str = @io.gets 11 | puts "|>#{str.chomp}<|" ## remove newline (w/ chomp) in debug/echo output 12 | str 13 | end 14 | end 15 | 16 | 17 | ## our own code 18 | require 'quik' 19 | -------------------------------------------------------------------------------- /test/test_colors.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ### 4 | # to run use 5 | # ruby -I ./lib -I ./test test/test_colors.rb 6 | 7 | 8 | require 'helper' 9 | 10 | 11 | class TestColors < MiniTest::Test 12 | 13 | def test_colors 14 | 15 | puts "this text is blue".blue 16 | puts "this text is red".red 17 | puts "this text is green".green + "this text is yellow".yellow 18 | puts "this text is magenta".magenta 19 | puts "this text is cyan".cyan 20 | 21 | puts "this text is blue".bold.blue 22 | puts "this text is red".bold.red 23 | puts "this text is green".bold.green + "this text is yellow".bold.yellow 24 | 25 | pp String.use_colors? 26 | 27 | assert true ## if we get here; everything is ok 28 | end 29 | 30 | def test_codes 31 | assert_equal "\e[31mred\e[0m", "red".red 32 | assert_equal "\e[34m\e[31mblue\e[0m\e[0m", "blue".red.blue 33 | ## better to generate "\e[34m;[31m" -- why? why not? e.g. one starting escpape and codes delimited by ; ?? 34 | assert_equal "\e[1mway bold\e[0m", "way bold".bold 35 | assert_equal "\e[36m\e[1mcyan bold\e[0m\e[0m", "cyan bold".bold.cyan 36 | end 37 | 38 | end # class TestColors 39 | 40 | -------------------------------------------------------------------------------- /test/test_config.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ### 4 | # to run use 5 | # ruby -I ./lib -I ./test test/test_config.rb 6 | 7 | 8 | require 'helper' 9 | 10 | 11 | class TestConfig < MiniTest::Test 12 | 13 | def test_config 14 | 15 | c = Quik::OpenConfig.new 16 | c.title = 'title' 17 | c.author.name = 'name' 18 | 19 | c.mrhyde.last_updated = Time.now 20 | c.mrhyde.title = 'title' 21 | c.mrhyde.name = 'name' 22 | c.mrhyde.theme = 'theme' 23 | c.mrhyde.meta.info = 'test nested nested value' 24 | 25 | ## test self reference in value assignment e.g. 26 | c.test.title = c.title 27 | c.test.name = c.author.name 28 | 29 | pp c.to_h 30 | 31 | assert true ## if we get here; everything is ok 32 | end 33 | 34 | 35 | end # class TestConfig 36 | -------------------------------------------------------------------------------- /test/test_merger.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ### 4 | # to run use 5 | # ruby -I ./lib -I ./test test/test_merger.rb 6 | 7 | 8 | require 'helper' 9 | 10 | 11 | class TestMerger < MiniTest::Test 12 | 13 | def test_gem_starter_template 14 | 15 | config = { 'filename' => 'hola', 16 | 'version' => '0.0.1', 17 | 'klass' => 'Hola', 18 | } 19 | 20 | m = Quik::Merger.new 21 | 22 | ## m.merge_filenames( "#{Quik.root}/test/data/gem-starter-template", config, test: true ) 23 | ## m.merge_filenames( "./o/gem", config ) 24 | 25 | ## note: set output path (for testing; not in place) 26 | ## m.merge_files( "#{Quik.root}/test/data/gem-starter-template", config, test: true, o: "./o/#{Time.now.to_i}" ) 27 | 28 | ## note: set output path (for testing; not in place) 29 | m.merge( "#{Quik.root}/test/data/gem-starter-template", config, test: true, o: "./o/#{Time.now.to_i}" ) 30 | ## m.merge( "./o/gem", config ) 31 | 32 | assert true ## if we get here; everything is ok 33 | end 34 | 35 | end # class TestMerger 36 | -------------------------------------------------------------------------------- /test/test_package.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ### 4 | # to run use 5 | # ruby -I ./lib -I ./test test/test_package.rb 6 | 7 | 8 | require 'helper' 9 | 10 | 11 | class TestPackage < MiniTest::Test 12 | 13 | def test_gem_starter_template 14 | 15 | pak = Quik::Package.new( 'gem-starter-template' ) 16 | pak.download 17 | pak.unzip( './o/gem' ) 18 | 19 | assert true ## if we get here; everything is ok 20 | end 21 | 22 | end # class TestPackage 23 | -------------------------------------------------------------------------------- /test/test_wizard.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ### 4 | # to run use 5 | # ruby -I ./lib -I ./test test/test_wizard.rb 6 | 7 | 8 | require 'helper' 9 | 10 | 11 | class TestWizard < MiniTest::Test 12 | 13 | include Quik::Wizard ## lets you use ask etc. 14 | 15 | def test_ask 16 | 17 | $QUIK_WIZARD_IN = EchoIO.new( <