copypasta 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: da680fd932e9deb4a0a86aeef581b1cf848afc7e00f5fc388a14f29d5e7d8bf1
4
+ data.tar.gz: 92e6652398bba04ce6acc64d7f517c49b2fb77beb5a1db3a02212b6c71744f3f
5
+ SHA512:
6
+ metadata.gz: a1461bc7f5f768f78045c768413bf6799019efab52040705a7f4127508d49cc433fa5d4728f4f8205f82aa589a1d331a2ba3ee9451064edafb0c6d7ae3b67322
7
+ data.tar.gz: 229e739dbfb85005203c58731200b09a14a06b56abbe57089bb853104b3c4f0e7b269cbd39bebad13c55a8eabd2e8fc0c732151043618d227ef8eda7ffb2f05e
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
@@ -0,0 +1,162 @@
1
+ # Relaxed.Ruby.Style
2
+ ## Version 2.1
3
+
4
+ Style/Alias:
5
+ Enabled: false
6
+ StyleGuide: http://relaxed.ruby.style/#stylealias
7
+
8
+ Style/AsciiComments:
9
+ Enabled: false
10
+ StyleGuide: http://relaxed.ruby.style/#styleasciicomments
11
+
12
+ Style/BeginBlock:
13
+ Enabled: false
14
+ StyleGuide: http://relaxed.ruby.style/#stylebeginblock
15
+
16
+ Style/BlockDelimiters:
17
+ Enabled: false
18
+ StyleGuide: http://relaxed.ruby.style/#styleblockdelimiters
19
+
20
+ Style/CommentAnnotation:
21
+ Enabled: false
22
+ StyleGuide: http://relaxed.ruby.style/#stylecommentannotation
23
+
24
+ Style/Documentation:
25
+ Enabled: false
26
+ StyleGuide: http://relaxed.ruby.style/#styledocumentation
27
+
28
+ Layout/DotPosition:
29
+ Enabled: false
30
+ StyleGuide: http://relaxed.ruby.style/#layoutdotposition
31
+
32
+ Style/DoubleNegation:
33
+ Enabled: false
34
+ StyleGuide: http://relaxed.ruby.style/#styledoublenegation
35
+
36
+ Style/EndBlock:
37
+ Enabled: false
38
+ StyleGuide: http://relaxed.ruby.style/#styleendblock
39
+
40
+ Style/FormatString:
41
+ Enabled: false
42
+ StyleGuide: http://relaxed.ruby.style/#styleformatstring
43
+
44
+ Style/IfUnlessModifier:
45
+ Enabled: false
46
+ StyleGuide: http://relaxed.ruby.style/#styleifunlessmodifier
47
+
48
+ Style/Lambda:
49
+ Enabled: false
50
+ StyleGuide: http://relaxed.ruby.style/#stylelambda
51
+
52
+ Style/ModuleFunction:
53
+ Enabled: false
54
+ StyleGuide: http://relaxed.ruby.style/#stylemodulefunction
55
+
56
+ Style/MultilineBlockChain:
57
+ Enabled: false
58
+ StyleGuide: http://relaxed.ruby.style/#stylemultilineblockchain
59
+
60
+ Style/NegatedIf:
61
+ Enabled: false
62
+ StyleGuide: http://relaxed.ruby.style/#stylenegatedif
63
+
64
+ Style/NegatedWhile:
65
+ Enabled: false
66
+ StyleGuide: http://relaxed.ruby.style/#stylenegatedwhile
67
+
68
+ Style/ParallelAssignment:
69
+ Enabled: false
70
+ StyleGuide: http://relaxed.ruby.style/#styleparallelassignment
71
+
72
+ Style/PercentLiteralDelimiters:
73
+ Enabled: false
74
+ StyleGuide: http://relaxed.ruby.style/#stylepercentliteraldelimiters
75
+
76
+ Style/PerlBackrefs:
77
+ Enabled: false
78
+ StyleGuide: http://relaxed.ruby.style/#styleperlbackrefs
79
+
80
+ Style/Semicolon:
81
+ Enabled: false
82
+ StyleGuide: http://relaxed.ruby.style/#stylesemicolon
83
+
84
+ Style/SignalException:
85
+ Enabled: false
86
+ StyleGuide: http://relaxed.ruby.style/#stylesignalexception
87
+
88
+ Style/SingleLineBlockParams:
89
+ Enabled: false
90
+ StyleGuide: http://relaxed.ruby.style/#stylesinglelineblockparams
91
+
92
+ Style/SingleLineMethods:
93
+ Enabled: false
94
+ StyleGuide: http://relaxed.ruby.style/#stylesinglelinemethods
95
+
96
+ Layout/SpaceBeforeBlockBraces:
97
+ Enabled: false
98
+ StyleGuide: http://relaxed.ruby.style/#layoutspacebeforeblockbraces
99
+
100
+ Layout/SpaceInsideParens:
101
+ Enabled: false
102
+ StyleGuide: http://relaxed.ruby.style/#layoutspaceinsideparens
103
+
104
+ Style/SpecialGlobalVars:
105
+ Enabled: false
106
+ StyleGuide: http://relaxed.ruby.style/#stylespecialglobalvars
107
+
108
+ Style/StringLiterals:
109
+ Enabled: false
110
+ StyleGuide: http://relaxed.ruby.style/#stylestringliterals
111
+
112
+ Style/TrailingCommaInArguments:
113
+ Enabled: false
114
+ StyleGuide: http://relaxed.ruby.style/#styletrailingcommainarguments
115
+
116
+ Style/TrailingCommaInLiteral:
117
+ Enabled: false
118
+ StyleGuide: http://relaxed.ruby.style/#styletrailingcommainliteral
119
+
120
+ Style/WhileUntilModifier:
121
+ Enabled: false
122
+ StyleGuide: http://relaxed.ruby.style/#stylewhileuntilmodifier
123
+
124
+ Style/WordArray:
125
+ Enabled: false
126
+ StyleGuide: http://relaxed.ruby.style/#stylewordarray
127
+
128
+ Lint/AmbiguousRegexpLiteral:
129
+ Enabled: false
130
+ StyleGuide: http://relaxed.ruby.style/#lintambiguousregexpliteral
131
+
132
+ Lint/AssignmentInCondition:
133
+ Enabled: false
134
+ StyleGuide: http://relaxed.ruby.style/#lintassignmentincondition
135
+
136
+ Metrics/AbcSize:
137
+ Enabled: false
138
+
139
+ Metrics/BlockNesting:
140
+ Enabled: false
141
+
142
+ Metrics/ClassLength:
143
+ Enabled: false
144
+
145
+ Metrics/ModuleLength:
146
+ Enabled: false
147
+
148
+ Metrics/CyclomaticComplexity:
149
+ Enabled: false
150
+
151
+ Metrics/LineLength:
152
+ Enabled: false
153
+
154
+ Metrics/MethodLength:
155
+ Enabled: false
156
+
157
+ Metrics/ParameterLists:
158
+ Enabled: false
159
+
160
+ Metrics/PerceivedComplexity:
161
+ Enabled: false
162
+
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at ed@edropple.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in copypasta.gemspec
6
+ gemspec
@@ -0,0 +1,36 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ copypasta (0.1.0)
5
+ cri-scaffold (~> 0.1)
6
+ highline (~> 1.7)
7
+ tilt (~> 2.0)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ coderay (1.1.2)
13
+ colored (1.2)
14
+ cri (2.10.1)
15
+ colored (~> 1.2)
16
+ cri-scaffold (0.1.1)
17
+ cri (~> 2.10)
18
+ highline (1.7.10)
19
+ method_source (0.9.0)
20
+ pry (0.11.3)
21
+ coderay (~> 1.1.0)
22
+ method_source (~> 0.9.0)
23
+ rake (10.5.0)
24
+ tilt (2.0.8)
25
+
26
+ PLATFORMS
27
+ ruby
28
+
29
+ DEPENDENCIES
30
+ bundler (~> 1.16)
31
+ copypasta!
32
+ pry
33
+ rake (~> 10.0)
34
+
35
+ BUNDLED WITH
36
+ 1.16.1
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Ed Ropple
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,82 @@
1
+ # Copypasta #
2
+ File generators are cool. Sometimes you need some scaffolding for things! But
3
+ unless you're using Thor (and Thor is also cool) or Rails (and Rails is not
4
+ cool), your options are pretty limited. Sometimes you can hack one together, in
5
+ an awkward way--shout out to anyone who's ever written a bunch of Chef!--but
6
+ there's no unified way that works on a CLI, embedded in your app, or in Chef.
7
+
8
+ So I decided to thereifixedit the whole thing.
9
+
10
+ ## Usage ##
11
+ Copypasta works on the concept of a `Plan`. A `Plan` is a directory structure
12
+ that mimics the final data structure while offering a DSL that figures out what,
13
+ exactly, should be dropped into the directory. Each directory in your `Plan`
14
+ must have a `_contents.rb` file that will be evaluated with our shiny happy
15
+ Copypasta DSL. `_contents.rb` will define files that live in that directory and
16
+ how they should be built:
17
+
18
+ - `copy` files are copied directly over; by default, `copy 'foo.txt'` will look
19
+ for a `foo.txt` at the same level as the `_contents.rb` file. You can specify
20
+ a specific file with `source:`.
21
+ - `erb` files are processed via Erubi; by default, `erb 'foo.txt'` will look for
22
+ a `foo.txt.erb` file. The plan's parameters will be passed to the erb file,
23
+ but additional local variables can be specified with the `parameters:` option.
24
+ You can specify a specific ERB template with `source:`.
25
+ - `download` fetches a file. `source:` is required.
26
+ - `literal` takes a Ruby string as `data:` and writes a file with the contents
27
+ of that string out to disk.
28
+
29
+ All content entries support the following options:
30
+
31
+ - `only_if`: Takes a `lambda |parameters| {}`; if truthy, writes out the file.
32
+
33
+ The root of your `Plan` should also contain a `_settings.rb` file which will
34
+ describe what parameters should be passed into the `Plan`. These parameters can
35
+ be retrieved interrogatively or passed in as data.
36
+
37
+ ### `_settings.rb` ###
38
+ - `parameter` declarations require a name (a `Symbol`) and a description (a
39
+ `String`). They can take the following options:
40
+ - `postprocess`: if interactively delivered, the string that comes out of
41
+ HighLine will be run through the specified 1-arity `Proc`. The returned
42
+ value will be stored in `parameters` as exposed to the runtime.
43
+
44
+ ### `_contents.rb` ###
45
+
46
+ ## Future Work/How You Can Help ##
47
+ - A Git handler for the `plan` content type would be a nice way to expand
48
+ Copypasta's usability.
49
+ - Parameter validation, both per-parameter and at a higher level (combinations
50
+ of parameters). The proper answer here is probably to use something like
51
+ [dry-validation](https://github.com/dry-rb/dry-validation) that can accept
52
+ Procs.
53
+
54
+ ## Development ##
55
+
56
+ After checking out the repo, run `bin/setup` to install dependencies. You can
57
+ also run `bin/console` for an interactive prompt that will allow you to
58
+ experiment.
59
+
60
+ To install this gem onto your local machine, run `bundle exec rake install`. To
61
+ release a new version, update the version number in `version.rb`, and then run
62
+ `bundle exec rake release`, which will create a git tag for the version, push
63
+ git commits and tags, and push the `.gem` file to
64
+ [rubygems.org](https://rubygems.org).
65
+
66
+ ## Contributing ##
67
+
68
+ Bug reports and pull requests are welcome on GitHub at
69
+ https://github.com/eropple/copypasta. This project is intended to be a safe,
70
+ welcoming space for collaboration, and contributors are expected to adhere to
71
+ the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
72
+
73
+ ## License ##
74
+
75
+ The gem is available as open source under the terms of the [MIT
76
+ License](https://opensource.org/licenses/MIT).
77
+
78
+ ## Code of Conduct
79
+
80
+ Everyone interacting in the Copypasta project’s codebases, issue trackers, chat
81
+ rooms and mailing lists is expected to follow the [code of
82
+ conduct](https://github.com/eropple/copypasta/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "copypasta"
5
+
6
+ require "pry"
7
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,30 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "copypasta/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "copypasta"
8
+ spec.version = Copypasta::VERSION
9
+ spec.authors = ["Ed Ropple"]
10
+ spec.email = ["ed@edropple.com"]
11
+
12
+ spec.summary = "A file and directory scaffolder for Ruby."
13
+ spec.homepage = "https://github.com/eropple/copypasta"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.16"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "pry"
26
+
27
+ spec.add_runtime_dependency "tilt", "~> 2.0"
28
+ spec.add_runtime_dependency "cri-scaffold", "~> 0.1"
29
+ spec.add_runtime_dependency "highline", "~> 1.7"
30
+ end
@@ -0,0 +1,10 @@
1
+ literal 'myliteral.txt', data: 5
2
+
3
+ copy 'mycopy.txt'
4
+ copy 'mycopy2.txt', source: 'myrenamed.txt'
5
+ erb 'mytemplate.txt', locals: { a: 10, b: 100 }
6
+
7
+ download 'my-ip.txt', source: "https://v4.ifconfig.co/ip"
8
+
9
+ literal 'only-if.txt', data: "the int list had a 2 in it",
10
+ only_if: ->(params) { params[:list_of_ints].include?(2) }
@@ -0,0 +1,7 @@
1
+ parameter :required, "This parameter is required."
2
+
3
+ parameter :defaulted, "This integer parameter is required, but defaults to 5.",
4
+ default: 5
5
+
6
+ parameter :list_of_ints, "Please provide a comma-separated list of integers.",
7
+ postprocess: ->(raw) { raw.split(",").map(&:strip).map(&:to_i) }
@@ -0,0 +1 @@
1
+ this file is copied directly
@@ -0,0 +1 @@
1
+ this file was myrenamed.txt but isn't when deployed.
@@ -0,0 +1,2 @@
1
+ required: <%= parameters[:required] %>
2
+ defaulted: <%= parameters[:defaulted] %>
@@ -0,0 +1,4 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "cri/scaffold"
4
+ Cri.scaffold("#{__dir__}/../lib/copypasta").run(ARGV)
@@ -0,0 +1,24 @@
1
+ require "copypasta/version"
2
+
3
+ require "copypasta/plan"
4
+
5
+ module Copypasta
6
+ def self.apply(plan_directory:, destination_directory:,
7
+ parameters:,
8
+ interactive: false,
9
+ force: false)
10
+ plan_directory = File.expand_path(plan_directory)
11
+ destination_directory = File.expand_path(destination_directory)
12
+
13
+ plan = Copypasta::Plan.from_directory(plan_directory)
14
+
15
+ full_parameters =
16
+ if interactive
17
+ plan.interrogate(parameters)
18
+ else
19
+ parameters
20
+ end
21
+
22
+ plan.apply(full_parameters, destination_directory, force: force)
23
+ end
24
+ end
@@ -0,0 +1,4 @@
1
+ run do |_opts, _args, _cmd|
2
+ puts cmd.help
3
+ exit 1
4
+ end
@@ -0,0 +1,21 @@
1
+ usage "PATH_TO_PLAN [DESTINATION]"
2
+
3
+ flag :f, :force, "applies plan even to a non-empty directory"
4
+
5
+ run do |opts, args, cmd|
6
+ unless (1..2).cover?(args.length)
7
+ puts cmd.help
8
+ exit 1
9
+ end
10
+
11
+ plan_directory = args[0]
12
+ destination_directory = args[1] || "."
13
+
14
+ require "copypasta"
15
+
16
+ Copypasta.apply(plan_directory: plan_directory,
17
+ destination_directory: destination_directory,
18
+ parameters: {},
19
+ interactive: true,
20
+ force: opts[:force])
21
+ end
@@ -0,0 +1,57 @@
1
+ require "copypasta/entry"
2
+
3
+ require 'fileutils'
4
+
5
+ module Copypasta
6
+ class Contents
7
+ attr_reader :target_directory
8
+ attr_accessor :force_create
9
+ attr_reader :entries
10
+
11
+ def initialize(target_directory)
12
+ @target_directory = target_directory.dup.freeze
13
+ @force_create = false
14
+ @entries = []
15
+ end
16
+
17
+ def apply(root, parameters)
18
+ content_dir = "#{root}/#{target_directory}"
19
+
20
+ if should_create?
21
+ FileUtils.mkdir_p content_dir
22
+ end
23
+
24
+ entries.each do |entry|
25
+ entry.apply(content_dir, parameters)
26
+ end
27
+ end
28
+
29
+ def should_create?
30
+ force_create || !entries.empty?
31
+ end
32
+
33
+ def self.from_tree(root)
34
+ require "copypasta/contents_dsl"
35
+
36
+ raise "#{root} doesn't exist." unless Dir.exist?(root)
37
+ root = File.expand_path(root)
38
+
39
+ items = []
40
+
41
+ Dir["#{root}/**/_contents.rb"].each do |f|
42
+ f = File.expand_path(f)
43
+ content_dir = File.dirname(f)
44
+ target_directory = content_dir.sub(root, "").sub(%r!^/!, "")
45
+
46
+ contents = Copypasta::Contents.new(target_directory)
47
+
48
+ dsl = Copypasta::ContentsDSL.new(contents, content_dir)
49
+ dsl.instance_eval File.read(f), f
50
+
51
+ items << contents unless contents.entries.empty?
52
+ end
53
+
54
+ items
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,36 @@
1
+ module Copypasta
2
+ class ContentsDSL
3
+ attr_reader :contents
4
+ attr_reader :directory
5
+
6
+ def initialize(contents, directory)
7
+ raise "contents must be a Copypasta::Contents" \
8
+ unless contents.is_a?(Copypasta::Contents)
9
+
10
+ raise "#{directory} doesn't exist." unless Dir.exist?(directory)
11
+
12
+ @contents = contents
13
+ @directory = directory
14
+ end
15
+
16
+ def create_even_if_empty!
17
+ contents.force_create = true
18
+ end
19
+
20
+ def copy(filename, source: nil, only_if: nil)
21
+ contents.entries << Copypasta::Entry::Copy.new(filename, directory: @directory, source: source, only_if: only_if).freeze
22
+ end
23
+
24
+ def erb(filename, source: nil, locals: {}, only_if: nil)
25
+ contents.entries << Copypasta::Entry::ERB.new(filename, directory: @directory, source: source, locals: locals, only_if: only_if).freeze
26
+ end
27
+
28
+ def download(filename, source: nil, only_if: nil)
29
+ contents.entries << Copypasta::Entry::Download.new(filename, source: source, only_if: only_if).freeze
30
+ end
31
+
32
+ def literal(filename, data:, only_if: nil)
33
+ contents.entries << Copypasta::Entry::Literal.new(filename, data: data, only_if: only_if).freeze
34
+ end
35
+ end
36
+ end
@@ -0,0 +1 @@
1
+ Dir["#{__dir__}/entry/*.rb"].each { |f| require_relative f }
@@ -0,0 +1,22 @@
1
+ module Copypasta
2
+ module Entry
3
+ class Base
4
+ attr_reader :filename
5
+
6
+ def apply(destination_directory, parameters)
7
+ do_apply(destination_directory, parameters) \
8
+ if @only_if.nil? || @only_if.call(parameters)
9
+ end
10
+
11
+ private
12
+
13
+ def do_apply(_destination_directory, _parameters)
14
+ raise "#{self.class.name}#do_apply(destination_directory, parameters) must be implemented."
15
+ end
16
+
17
+ def target_file(destination_directory)
18
+ "#{destination_directory}/#{@filename}"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ require "copypasta/entry/base"
2
+
3
+ require "fileutils"
4
+
5
+ module Copypasta
6
+ module Entry
7
+ class Copy < Copypasta::Entry::Base
8
+ attr_reader :source
9
+
10
+ def initialize(filename, directory:, source: nil, only_if: nil)
11
+ raise "filename must be a string." unless filename.is_a?(String)
12
+ raise "only_if must be null or a Proc." if !only_if.nil? && !only_if.is_a?(Proc)
13
+
14
+ @filename = filename
15
+ @directory = directory
16
+ @only_if = only_if
17
+
18
+ @source = source || filename
19
+ end
20
+
21
+ private
22
+
23
+ def do_apply(destination, _parameters)
24
+ FileUtils.cp("#{@directory}/#{@source}", target_file(destination))
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ require "copypasta/entry/base"
2
+
3
+ require "uri"
4
+
5
+ module Copypasta
6
+ module Entry
7
+ class Download < Copypasta::Entry::Base
8
+ attr_reader :source
9
+
10
+ def initialize(filename, source:, only_if: nil)
11
+ raise "filename must be a string." unless filename.is_a?(String)
12
+ raise "source url '#{source}' looks invalid." \
13
+ unless source =~ URI::DEFAULT_PARSER.make_regexp
14
+ raise "only_if must be null or a Proc." if !only_if.nil? && !only_if.is_a?(Proc)
15
+
16
+ @filename = filename
17
+ @source = source
18
+ @only_if = only_if
19
+ end
20
+
21
+ private
22
+
23
+ def do_apply(destination, _parameters)
24
+ require "open-uri"
25
+
26
+ download = open(@source)
27
+ IO.copy_stream(download, target_file(destination))
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ require "copypasta/entry/base"
2
+
3
+ require "tilt/erb"
4
+
5
+ module Copypasta
6
+ module Entry
7
+ class ERB < Copypasta::Entry::Base
8
+ attr_reader :source
9
+ attr_reader :locals
10
+
11
+ def initialize(filename, directory:, locals: {}, source: nil, only_if: nil)
12
+ raise "filename must be a string." unless filename.is_a?(String)
13
+ raise "only_if must be null or a Proc." if !only_if.nil? && !only_if.is_a?(Proc)
14
+ raise "locals must be a Hash." unless locals.is_a?(Hash)
15
+
16
+ @filename = filename.dup.freeze
17
+ @directory = directory.dup.freeze
18
+ @locals = locals.dup.freeze
19
+ @source = (source || "#{filename}.erb").dup.freeze
20
+ @only_if = only_if
21
+ end
22
+
23
+ private
24
+
25
+ def do_apply(destination, parameters)
26
+ template = Tilt::ERBTemplate.new("#{@directory}/#{@source}")
27
+ output = template.render(Object.new, @locals.merge(parameters: parameters))
28
+
29
+ IO.write(target_file(destination), output)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,24 @@
1
+ require "copypasta/entry/base"
2
+
3
+ module Copypasta
4
+ module Entry
5
+ class Literal < Copypasta::Entry::Base
6
+ attr_reader :data
7
+
8
+ def initialize(filename, data:, only_if: nil)
9
+ raise "filename must be a string." unless filename.is_a?(String)
10
+ raise "only_if must be null or a Proc." if !only_if.nil? && !only_if.is_a?(Proc)
11
+
12
+ @filename = filename
13
+ @data = data
14
+ @only_if = only_if
15
+ end
16
+
17
+ private
18
+
19
+ def do_apply(destination, _parameters)
20
+ IO.write(target_file(destination), data.to_s)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ module Copypasta
2
+ class ParameterDefinition
3
+ attr_reader :name
4
+ attr_reader :description
5
+ attr_reader :default
6
+ attr_reader :postprocess
7
+
8
+ def initialize(name, description, default, postprocess)
9
+ raise "name must be nil or a Symbol." \
10
+ unless name.is_a?(Symbol) || name.nil?
11
+ @name = name
12
+
13
+ raise "description must be nil or a String." \
14
+ unless description.is_a?(String) || description.nil?
15
+ @description = description
16
+
17
+ @default = default
18
+
19
+ raise "postprocess must be nil or a String." \
20
+ unless postprocess.is_a?(Proc) || postprocess.nil?
21
+ @postprocess = postprocess
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,84 @@
1
+ require "copypasta/settings"
2
+ require "copypasta/contents"
3
+
4
+ module Copypasta
5
+ class Plan
6
+ attr_reader :root
7
+ attr_reader :settings
8
+ attr_reader :contents
9
+
10
+ def initialize(settings:, contents: [])
11
+ raise "settings must be a Copypasta::Settings" \
12
+ unless settings.is_a?(Copypasta::Settings)
13
+
14
+ raise "'contents' must be an Array of Copypasta::Contents." \
15
+ unless contents.is_a?(Array) && contents.all? { |c| c.is_a?(Copypasta::Contents) }
16
+
17
+ @root = root.dup.freeze
18
+ @settings = settings.dup.freeze
19
+ @contents = contents.dup.freeze
20
+ end
21
+
22
+ def interrogate(parameters)
23
+ raise "#interrogate can only be called when STDOUT is a tty?." \
24
+ unless STDOUT.tty?
25
+ # TODO: check to see if the parameter exists; if it doesn't, ask on the
26
+ # tty for a value.
27
+ # TODO: decide whether to notify-and-retry or fail on invalid parameter.
28
+
29
+ require "highline"
30
+ cli = HighLine.new
31
+
32
+ missing_parameters =
33
+ settings.parameter_definitions
34
+ .values.select { |pd| parameters[pd.name].nil? }
35
+
36
+ missing_parameters.each do |pd|
37
+ puts pd.description
38
+ answer = cli.ask("#{pd.name}: ") { |q| q.default = pd.default }
39
+
40
+ answer = pd.postprocess.call(answer) unless pd.postprocess.nil?
41
+
42
+ puts "Received parameter '#{pd.name}': '#{answer}'"
43
+
44
+ parameters[pd.name] = answer
45
+ end
46
+
47
+ parameters
48
+ end
49
+
50
+ def parameters_valid?(parameters)
51
+ ret = @settings.validate(parameters)
52
+
53
+ ret.all?(&:empty?)
54
+ end
55
+
56
+ def apply(parameters, destination_directory, force: false)
57
+ # TODO: validate the parameters, then call all of the entries with the
58
+ # parameter set
59
+
60
+ raise "parameter set is invalid, check the logs." \
61
+ unless parameters_valid?(parameters)
62
+ raise "Directory is dirty. Use force to create anyway." \
63
+ if !force && !directory_clean?(destination_directory)
64
+
65
+ contents.each { |c| c.apply(destination_directory, parameters) }
66
+ end
67
+
68
+ def self.from_directory(root)
69
+ raise "#{root} doesn't exist." unless Dir.exist?(root)
70
+ root = File.expand_path(root)
71
+
72
+ settings = Copypasta::Settings.from_file("#{root}/_settings.rb")
73
+ contents = Copypasta::Contents.from_tree(root)
74
+
75
+ Copypasta::Plan.new(settings: settings, contents: contents)
76
+ end
77
+
78
+ private
79
+
80
+ def directory_clean?(destination)
81
+ Dir["#{destination}/*"].reject { |f| f == ".." || f == "." }.empty?
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,22 @@
1
+ require "dry/validation"
2
+
3
+ require "copypasta/parameter_definition"
4
+
5
+ module Copypasta
6
+ class Settings
7
+ attr_reader :parameter_definitions
8
+
9
+ def initialize
10
+ @parameter_definitions = {}
11
+ end
12
+
13
+ def self.from_file(path)
14
+ require "copypasta/settings_dsl"
15
+ raise "#{path} doesn't exist." unless File.exist?(path)
16
+
17
+ dsl = Copypasta::SettingsDSL.new
18
+ dsl.instance_eval File.read(path), path
19
+ dsl.settings
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,67 @@
1
+ require "yaml"
2
+ require "json"
3
+
4
+ require "dry/validation"
5
+
6
+ require "copypasta/settings"
7
+ require "copypasta/parameter_definition"
8
+
9
+ module Copypasta
10
+ class SettingsDSL
11
+ attr_reader :settings
12
+
13
+ def initialize(settings = nil)
14
+ raise "settings must be a Copypasta::Settings." \
15
+ if !settings.nil? && !settings.is_a?(Copypasta::Settings)
16
+
17
+ @settings = settings || Copypasta::Settings.new
18
+ end
19
+
20
+ def parameter(name, description, default: nil, postprocess: nil)
21
+ name = name.to_sym
22
+ raise "Duplicate parameter '#{name}' detected." \
23
+ if settings.parameter_definitions.key?(name)
24
+
25
+ param = Copypasta::ParameterDefinition.new(name, description,
26
+ default,
27
+ postprocess)
28
+ param.freeze
29
+
30
+ settings.parameter_definitions[name] = param
31
+ end
32
+
33
+ def yaml(name, description)
34
+ parameter(name, description,
35
+ postprocess: ->(raw) {
36
+ if raw.is_a?(String)
37
+ raw
38
+ else
39
+ YAML.safe_load(raw)
40
+ end
41
+ })
42
+ end
43
+
44
+ def json(name, description)
45
+ parameter(name, description,
46
+ postprocess: ->(raw) {
47
+ if raw.is_a?(String)
48
+ raw
49
+ else
50
+ JSON.parse(raw)
51
+ end
52
+ })
53
+ end
54
+
55
+ def csv(name, description, postprocess: nil)
56
+ parameter(name, description,
57
+ postprocess: ->(raw) {
58
+ r = raw.split(",").map(&:strip)
59
+ if postprocess
60
+ postprocess.call(r)
61
+ else
62
+ r
63
+ end
64
+ })
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ module Copypasta
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: copypasta
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ed Ropple
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-01-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: tilt
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: cri-scaffold
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.1'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: highline
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.7'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.7'
97
+ description:
98
+ email:
99
+ - ed@edropple.com
100
+ executables:
101
+ - copypasta
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rubocop.yml"
107
+ - CODE_OF_CONDUCT.md
108
+ - Gemfile
109
+ - Gemfile.lock
110
+ - LICENSE.txt
111
+ - README.md
112
+ - Rakefile
113
+ - bin/console
114
+ - bin/setup
115
+ - copypasta.gemspec
116
+ - example/_contents.rb
117
+ - example/_settings.rb
118
+ - example/mycopy.txt
119
+ - example/myrenamed.txt
120
+ - example/mytemplate.txt.erb
121
+ - exe/copypasta
122
+ - lib/copypasta.rb
123
+ - lib/copypasta/cli.rb
124
+ - lib/copypasta/cli/apply.cli.rb
125
+ - lib/copypasta/contents.rb
126
+ - lib/copypasta/contents_dsl.rb
127
+ - lib/copypasta/entry.rb
128
+ - lib/copypasta/entry/base.rb
129
+ - lib/copypasta/entry/copy.rb
130
+ - lib/copypasta/entry/download.rb
131
+ - lib/copypasta/entry/erb.rb
132
+ - lib/copypasta/entry/literal.rb
133
+ - lib/copypasta/parameter_definition.rb
134
+ - lib/copypasta/plan.rb
135
+ - lib/copypasta/settings.rb
136
+ - lib/copypasta/settings_dsl.rb
137
+ - lib/copypasta/version.rb
138
+ homepage: https://github.com/eropple/copypasta
139
+ licenses:
140
+ - MIT
141
+ metadata: {}
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubyforge_project:
158
+ rubygems_version: 2.7.3
159
+ signing_key:
160
+ specification_version: 4
161
+ summary: A file and directory scaffolder for Ruby.
162
+ test_files: []