s3_website_monadic 0.0.29 → 0.0.30

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e67a3b50d099328d1163b4ab28bb19ab5ea70bb2
4
- data.tar.gz: 87cb81ba31dcc20734987b90d194ea95a5d7d84a
3
+ metadata.gz: a489b6afb931e8384aa6d656acd8bf23a0c366be
4
+ data.tar.gz: 12234bf6f3689a83ad37a61c7029ce80900e2a67
5
5
  SHA512:
6
- metadata.gz: dde895e0d896fca1d47dfa8bb5b2ed599f698c037ef4c3cb870cbe4cfe86603964b0eb87fed90e2d45f1253b79a59600f995653fcbe31470a95e955a5544d074
7
- data.tar.gz: 479a0f6a3c586f26833a0fc2076bddbcc94618b2b40de911422337e874a3fa4c2018be98662e19571dfa81fab508776e9333f888b455a9eac66962d42218fbc4
6
+ metadata.gz: bbc33e009ccbe805fe72697433d59fd4c82691cc9edd3e95d4e8277fceb159715f8b0d4566fa2c1e85d092c20cab2eb37178e0cfde0db1fc2103682f90653a67
7
+ data.tar.gz: 6c8b915eab3220b1a7148d0912c267dfe7b05ac653367b033e83c9c6144d027d22d18ae2ec820314415b46433b0ec5335616a8f2cda730c46bab28577dbc8528
@@ -44,13 +44,11 @@ class Cli < Thor
44
44
  option(
45
45
  :site,
46
46
  :type => :string,
47
- :default => 'infer automatically',
48
- :desc => "The directory where your website files are. When not defined, s3_website will look for the site in #{S3Website::Paths.site_paths.join(' or ')}."
47
+ :desc => "The directory where your website files are. When not defined, s3_website will look for the site in either _site or public/output."
49
48
  )
50
49
  option(
51
50
  :config_dir,
52
51
  :type => :string,
53
- :default => Dir.pwd,
54
52
  :desc => "The directory where your config file is. When not defined, s3_website will look in the current working directory."
55
53
  )
56
54
  option(
@@ -74,11 +72,10 @@ class Cli < Thor
74
72
  call_dir = Dir.pwd
75
73
  project_root = File.expand_path(File.dirname(__FILE__)+ '/..')
76
74
  logger = Logger.new(options[:verbose])
77
- Dir.chdir(project_root) {
78
- jar_file = resolve_jar project_root, logger
79
- # Then run it
80
- run_s3_website_jar(jar_file, call_dir, logger)
81
- }
75
+ # Find the jar
76
+ jar_file = resolve_jar project_root, logger
77
+ # Then run it
78
+ run_s3_website_jar(jar_file, call_dir, logger)
82
79
  end
83
80
 
84
81
  desc 'cfg SUBCOMMAND ...ARGS', 'Operate on the config file'
@@ -86,15 +83,7 @@ class Cli < Thor
86
83
  end
87
84
 
88
85
  def run_s3_website_jar(jar_file, call_dir, logger)
89
- site_path = File.expand_path(S3Website::Paths.infer_site_path options[:site], call_dir)
90
- config_dir = File.expand_path options[:config_dir]
91
-
92
- args = [
93
- "--site=#{site_path}",
94
- "--config-dir=#{config_dir}",
95
- "#{'--verbose' if options[:verbose]}",
96
- "#{'--dry-run' if options[:dry_run]}"
97
- ].join ' '
86
+ args = ARGV.join(' ').sub('push', '')
98
87
  logger.debug_msg "Using #{jar_file}"
99
88
  if system("java -cp #{jar_file} s3.website.Push #{args}")
100
89
  exit 0
@@ -121,7 +110,9 @@ def resolve_jar(project_root, logger)
121
110
  else
122
111
  is_development = File.exists?(project_root + '/.git')
123
112
  if is_development
124
- system "./sbt assembly"
113
+ Dir.chdir(project_root) {
114
+ system "./sbt assembly"
115
+ }
125
116
  development_jar_path
126
117
  else
127
118
  download_jar(released_jar_lookup_paths, logger)
data/changelog.md CHANGED
@@ -4,8 +4,11 @@ This project uses [Semantic Versioning](http://semver.org).
4
4
 
5
5
  ## 2.0.0
6
6
 
7
- This release contains backward breaking changes. Please read the section below
8
- for more info.
7
+ ### New features
8
+
9
+ * Simulate deployments with `push --dry-run`
10
+
11
+ * Support CloudFront invalidations when the site contains over 3000 files
9
12
 
10
13
  ### Java is now required
11
14
 
@@ -1,3 +1,3 @@
1
1
  module S3Website
2
- VERSION = '0.0.29'
2
+ VERSION = '0.0.30'
3
3
  end
data/lib/s3_website.rb CHANGED
@@ -2,6 +2,3 @@ require 'rubygems'
2
2
  require 'thor'
3
3
 
4
4
  require File.dirname(__FILE__) + "/s3_website/version"
5
- require File.dirname(__FILE__) + "/s3_website/paths"
6
- require File.dirname(__FILE__) + "/s3_website/jekyll"
7
- require File.dirname(__FILE__) + "/s3_website/nanoc"
@@ -25,6 +25,8 @@ import s3.website.CloudFront.CloudFrontSetting
25
25
  import s3.website.S3.SuccessfulUpload
26
26
  import s3.website.CloudFront.FailedInvalidation
27
27
  import scala.Int
28
+ import java.io.File
29
+ import com.lexicalscope.jewel.cli.CliFactory.parseArguments
28
30
 
29
31
  object Push {
30
32
 
@@ -185,35 +187,44 @@ object Push {
185
187
  trait CliArgs {
186
188
  import com.lexicalscope.jewel.cli.Option
187
189
 
188
- @Option def site: String
189
- @Option(longName = Array("config-dir")) def configDir: String
190
+ @Option(defaultToNull = true) def site: String
191
+ @Option(longName = Array("config-dir"), defaultToNull = true) def configDir: String
190
192
  @Option def verbose: Boolean
191
193
  @Option(longName = Array("dry-run")) def dryRun: Boolean
192
194
  }
193
195
 
194
196
  def main(args: Array[String]) {
195
- val cliArgs = CliFactory.parseArguments(classOf[CliArgs], args:_*)
197
+ implicit val cliArgs = parseArguments(classOf[CliArgs], args:_*)
196
198
  implicit val s3Settings = S3Setting()
197
199
  implicit val cloudFrontSettings = CloudFrontSetting()
200
+ implicit val workingDirectory = new File(System.getProperty("user.dir")).getAbsoluteFile
201
+ System exit push
202
+ }
203
+
204
+ def push(implicit cliArgs: CliArgs, s3Settings: S3Setting, cloudFrontSettings: CloudFrontSetting, workingDirectory: File): ExitCode = {
198
205
  implicit val logger: Logger = new Logger(cliArgs.verbose)
199
206
  implicit val pushMode = new PushMode {
200
207
  def dryRun = cliArgs.dryRun
201
208
  }
202
- val errorOrPushStatus = push(siteInDirectory = cliArgs.site, withConfigDirectory = cliArgs.configDir)
203
- errorOrPushStatus.left foreach (err => logger.fail(s"Could not load the site: ${err.reportMessage}"))
204
- System exit errorOrPushStatus.fold(_ => 1, pushStatus => pushStatus)
205
- }
206
209
 
207
- def push(siteInDirectory: String, withConfigDirectory: String)
208
- (implicit s3Settings: S3Setting, cloudFrontSettings: CloudFrontSetting, logger: Logger, pushMode: PushMode) =
209
- loadSite(withConfigDirectory + "/s3_website.yml", siteInDirectory)
210
- .right
211
- .map {
212
- implicit site =>
213
- val threadPool = newFixedThreadPool(site.config.concurrency_level)
214
- implicit val executor = fromExecutor(threadPool)
215
- val pushStatus = pushSite
216
- threadPool.shutdownNow()
217
- pushStatus
210
+ val errorOrSiteDir: ErrorOrFile =
211
+ Option(cliArgs.site).fold(Ssg.findSiteDirectory(workingDirectory))(siteDirFromCli => Right(new File(siteDirFromCli)))
212
+ def errorOrSite(siteInDirectory: File): Either[ErrorReport, Site] =
213
+ loadSite(Option(cliArgs.configDir).getOrElse(workingDirectory.getPath) + "/s3_website.yml", siteInDirectory.getAbsolutePath)
214
+
215
+ val errorOrPushStatus = for {
216
+ siteInDirectory <- errorOrSiteDir.right
217
+ loadedSite <- errorOrSite(siteInDirectory).right
218
+ } yield {
219
+ implicit val site = loadedSite
220
+ val threadPool = newFixedThreadPool(site.config.concurrency_level)
221
+ implicit val executor = fromExecutor(threadPool)
222
+ val pushStatus = pushSite
223
+ threadPool.shutdownNow()
224
+ pushStatus
218
225
  }
226
+
227
+ errorOrPushStatus.left foreach (err => logger.fail(s"Could not load the site: ${err.reportMessage}"))
228
+ errorOrPushStatus fold((err: ErrorReport) => 1, pushStatus => pushStatus)
229
+ }
219
230
  }
@@ -0,0 +1,33 @@
1
+ package s3.website.model
2
+
3
+ import java.io.File
4
+ import s3.website.{ErrorOrFile, ErrorReport}
5
+
6
+ // ssg = static site generator
7
+ trait Ssg {
8
+ def outputDirectory: String
9
+ }
10
+
11
+ object Ssg {
12
+ val automaticallySupportedSiteGenerators = Jekyll :: Nanoc :: Nil
13
+
14
+ val notFoundErrorReport =
15
+ new ErrorReport {
16
+ def reportMessage =
17
+ """|Could not find a website in any of the pre-defined directories.
18
+ |Specify the website location with the --site=path argument and try again.""".stripMargin
19
+ }
20
+
21
+ def findSiteDirectory(workingDirectory: File): ErrorOrFile =
22
+ LocalFile.recursiveListFiles(workingDirectory).find { file =>
23
+ file.isDirectory && automaticallySupportedSiteGenerators.exists(_.outputDirectory == file.getName)
24
+ }.fold(Left(notFoundErrorReport): ErrorOrFile)(Right(_))
25
+ }
26
+
27
+ case object Jekyll extends Ssg {
28
+ def outputDirectory = "_site"
29
+ }
30
+
31
+ case object Nanoc extends Ssg {
32
+ def outputDirectory = "public/output"
33
+ }
@@ -6,6 +6,7 @@ import s3.website.Utils._
6
6
  import s3.website.S3.{PushSuccessReport, PushFailureReport}
7
7
  import com.amazonaws.AmazonServiceException
8
8
  import s3.website.model.{Config, Site}
9
+ import java.io.File
9
10
 
10
11
  package object website {
11
12
  trait Report {
@@ -94,4 +95,6 @@ package object website {
94
95
  }
95
96
 
96
97
  implicit def site2Config(implicit site: Site): Config = site.config
98
+
99
+ type ErrorOrFile = Either[ErrorReport, File]
97
100
  }
@@ -1,6 +1,6 @@
1
1
  package s3.website
2
2
 
3
- import org.specs2.mutable.{After, Specification}
3
+ import org.specs2.mutable.{BeforeAfter, After, Specification}
4
4
  import s3.website.model._
5
5
  import org.specs2.specification.Scope
6
6
  import org.apache.commons.io.FileUtils
@@ -10,7 +10,6 @@ import org.mockito.{Mockito, Matchers, ArgumentCaptor}
10
10
  import org.mockito.Mockito._
11
11
  import com.amazonaws.services.s3.AmazonS3
12
12
  import com.amazonaws.services.s3.model._
13
- import scala.concurrent.ExecutionContext.Implicits.global
14
13
  import scala.concurrent.duration._
15
14
  import s3.website.S3.S3Setting
16
15
  import scala.collection.JavaConversions._
@@ -22,25 +21,26 @@ import com.amazonaws.services.cloudfront.model.{CreateInvalidationResult, Create
22
21
  import org.mockito.stubbing.Answer
23
22
  import org.mockito.invocation.InvocationOnMock
24
23
  import java.util.concurrent.atomic.AtomicInteger
25
- import org.apache.commons.io.FileUtils.write
24
+ import org.apache.commons.io.FileUtils.{forceDelete, forceMkdir, write}
26
25
  import scala.collection.mutable
26
+ import s3.website.Push.CliArgs
27
27
 
28
28
  class S3WebsiteSpec extends Specification {
29
29
 
30
30
  "gzip: true" should {
31
- "update a gzipped S3 object if the contents has changed" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
31
+ "update a gzipped S3 object if the contents has changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
32
32
  config = "gzip: true"
33
33
  setLocalFileWithContent(("styles.css", "<h1>hi again</h1>"))
34
34
  setS3Files(S3File("styles.css", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */))
35
- Push.pushSite
35
+ Push.push
36
36
  sentPutObjectRequest.getKey must equalTo("styles.css")
37
37
  }
38
38
 
39
- "not update a gzipped S3 object if the contents has not changed" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
39
+ "not update a gzipped S3 object if the contents has not changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
40
40
  config = "gzip: true"
41
41
  setLocalFileWithContent(("styles.css", "<h1>hi</h1>"))
42
42
  setS3Files(S3File("styles.css", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */))
43
- Push.pushSite
43
+ Push.push
44
44
  noUploadsOccurred must beTrue
45
45
  }
46
46
  }
@@ -49,159 +49,159 @@ class S3WebsiteSpec extends Specification {
49
49
  gzip:
50
50
  - .xml
51
51
  """ should {
52
- "update a gzipped S3 object if the contents has changed" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
52
+ "update a gzipped S3 object if the contents has changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
53
53
  config = """
54
54
  |gzip:
55
55
  | - .xml
56
56
  """.stripMargin
57
57
  setLocalFileWithContent(("file.xml", "<h1>hi again</h1>"))
58
58
  setS3Files(S3File("file.xml", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */))
59
- Push.pushSite
59
+ Push.push
60
60
  sentPutObjectRequest.getKey must equalTo("file.xml")
61
61
  }
62
62
  }
63
63
 
64
64
  "push" should {
65
- "not upload a file if it has not changed" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
65
+ "not upload a file if it has not changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
66
66
  setLocalFileWithContent(("index.html", "<div>hello</div>"))
67
67
  setS3Files(S3File("index.html", md5Hex("<div>hello</div>")))
68
- Push.pushSite
68
+ Push.push
69
69
  noUploadsOccurred must beTrue
70
70
  }
71
71
 
72
- "update a file if it has changed" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
72
+ "update a file if it has changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
73
73
  setLocalFileWithContent(("index.html", "<h1>old text</h1>"))
74
74
  setS3Files(S3File("index.html", md5Hex("<h1>new text</h1>")))
75
- Push.pushSite
75
+ Push.push
76
76
  sentPutObjectRequest.getKey must equalTo("index.html")
77
77
  }
78
78
 
79
- "create a file if does not exist on S3" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
79
+ "create a file if does not exist on S3" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
80
80
  setLocalFile("index.html")
81
- Push.pushSite
81
+ Push.push
82
82
  sentPutObjectRequest.getKey must equalTo("index.html")
83
83
  }
84
84
 
85
- "delete files that are on S3 but not on local file system" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
85
+ "delete files that are on S3 but not on local file system" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
86
86
  setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
87
- Push.pushSite
87
+ Push.push
88
88
  sentDelete must equalTo("old.html")
89
89
  }
90
90
 
91
- "try again if the upload fails" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
91
+ "try again if the upload fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
92
92
  setLocalFile("index.html")
93
93
  uploadFailsAndThenSucceeds(howManyFailures = 5)
94
- Push.pushSite
94
+ Push.push
95
95
  verify(amazonS3Client, times(6)).putObject(Matchers.any(classOf[PutObjectRequest]))
96
96
  }
97
97
 
98
- "not try again if the upload fails on because of invalid credentials" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
98
+ "not try again if the upload fails on because of invalid credentials" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
99
99
  setLocalFile("index.html")
100
100
  when(amazonS3Client.putObject(Matchers.any(classOf[PutObjectRequest]))).thenThrow {
101
101
  val e = new AmazonServiceException("your credentials are incorrect")
102
102
  e.setStatusCode(403)
103
103
  e
104
104
  }
105
- Push.pushSite
105
+ Push.push
106
106
  verify(amazonS3Client, times(1)).putObject(Matchers.any(classOf[PutObjectRequest]))
107
107
  }
108
108
 
109
- "try again if the delete fails" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
109
+ "try again if the delete fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
110
110
  setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
111
111
  deleteFailsAndThenSucceeds(howManyFailures = 5)
112
- Push.pushSite
112
+ Push.push
113
113
  verify(amazonS3Client, times(6)).deleteObject(Matchers.anyString(), Matchers.anyString())
114
114
  }
115
115
 
116
- "try again if the object listing fails" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
116
+ "try again if the object listing fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
117
117
  setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
118
118
  objectListingFailsAndThenSucceeds(howManyFailures = 5)
119
- Push.pushSite
119
+ Push.push
120
120
  verify(amazonS3Client, times(6)).listObjects(Matchers.any(classOf[ListObjectsRequest]))
121
121
  }
122
122
  }
123
123
 
124
124
  "push with CloudFront" should {
125
- "invalidate the updated CloudFront items" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
125
+ "invalidate the updated CloudFront items" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
126
126
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
127
127
  setLocalFiles("css/test.css", "articles/index.html")
128
128
  setOutdatedS3Keys("css/test.css", "articles/index.html")
129
- Push.pushSite
129
+ Push.push
130
130
  sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/css/test.css" :: "/articles/index.html" :: Nil).sorted)
131
131
  }
132
132
 
133
- "not send CloudFront invalidation requests on new objects" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
133
+ "not send CloudFront invalidation requests on new objects" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
134
134
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
135
135
  setLocalFile("newfile.js")
136
- Push.pushSite
136
+ Push.push
137
137
  noInvalidationsOccurred must beTrue
138
138
  }
139
139
 
140
- "not send CloudFront invalidation requests on redirect objects" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
140
+ "not send CloudFront invalidation requests on redirect objects" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
141
141
  config = """
142
142
  |cloudfront_distribution_id: EGM1J2JJX9Z
143
143
  |redirects:
144
144
  | /index.php: index.html
145
145
  """.stripMargin
146
- Push.pushSite
146
+ Push.push
147
147
  noInvalidationsOccurred must beTrue
148
148
  }
149
149
 
150
- "retry CloudFront responds with TooManyInvalidationsInProgressException" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
150
+ "retry CloudFront responds with TooManyInvalidationsInProgressException" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
151
151
  setTooManyInvalidationsInProgress(4)
152
152
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
153
153
  setLocalFile("test.css")
154
154
  setOutdatedS3Keys("test.css")
155
- Push.pushSite must equalTo(0) // The retries should finally result in a success
155
+ Push.push must equalTo(0) // The retries should finally result in a success
156
156
  sentInvalidationRequests.length must equalTo(4)
157
157
  }
158
158
 
159
- "retry if CloudFront is temporarily unreachable" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
159
+ "retry if CloudFront is temporarily unreachable" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
160
160
  invalidationsFailAndThenSucceed(5)
161
161
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
162
162
  setLocalFile("test.css")
163
163
  setOutdatedS3Keys("test.css")
164
- Push.pushSite
164
+ Push.push
165
165
  sentInvalidationRequests.length must equalTo(6)
166
166
  }
167
167
 
168
- "encode unsafe characters in the keys" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
168
+ "encode unsafe characters in the keys" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
169
169
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
170
170
  setLocalFile("articles/arnold's file.html")
171
171
  setOutdatedS3Keys("articles/arnold's file.html")
172
- Push.pushSite
172
+ Push.push
173
173
  sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/articles/arnold's%20file.html" :: Nil).sorted)
174
174
  }
175
175
 
176
- "invalidate the root object '/' if a top-level object is updated or deleted" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
176
+ "invalidate the root object '/' if a top-level object is updated or deleted" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
177
177
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
178
178
  setLocalFile("maybe-index.html")
179
179
  setOutdatedS3Keys("maybe-index.html")
180
- Push.pushSite
180
+ Push.push
181
181
  sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/" :: "/maybe-index.html" :: Nil).sorted)
182
182
  }
183
183
  }
184
184
 
185
185
  "cloudfront_invalidate_root: true" should {
186
- "convert CloudFront invalidation paths with the '/index.html' suffix into '/'" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
186
+ "convert CloudFront invalidation paths with the '/index.html' suffix into '/'" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
187
187
  config = """
188
188
  |cloudfront_distribution_id: EGM1J2JJX9Z
189
189
  |cloudfront_invalidate_root: true
190
190
  """.stripMargin
191
191
  setLocalFile("articles/index.html")
192
192
  setOutdatedS3Keys("articles/index.html")
193
- Push.pushSite
193
+ Push.push
194
194
  sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/articles/" :: Nil).sorted)
195
195
  }
196
196
  }
197
197
 
198
198
  "a site with over 1000 items" should {
199
- "split the CloudFront invalidation requests into batches of 1000 items" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
199
+ "split the CloudFront invalidation requests into batches of 1000 items" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
200
200
  val files = (1 to 1002).map { i => s"lots-of-files/file-$i"}
201
201
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
202
202
  setLocalFiles(files:_*)
203
203
  setOutdatedS3Keys(files:_*)
204
- Push.pushSite
204
+ Push.push
205
205
  sentInvalidationRequests.length must equalTo(2)
206
206
  sentInvalidationRequests(0).getInvalidationBatch.getPaths.getItems.length must equalTo(1000)
207
207
  sentInvalidationRequests(1).getInvalidationBatch.getPaths.getItems.length must equalTo(2)
@@ -209,73 +209,73 @@ class S3WebsiteSpec extends Specification {
209
209
  }
210
210
 
211
211
  "push exit status" should {
212
- "be 0 all uploads succeed" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
212
+ "be 0 all uploads succeed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
213
213
  setLocalFiles("file.txt")
214
- Push.pushSite must equalTo(0)
214
+ Push.push must equalTo(0)
215
215
  }
216
216
 
217
- "be 1 if any of the uploads fails" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
217
+ "be 1 if any of the uploads fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
218
218
  setLocalFiles("file.txt")
219
219
  when(amazonS3Client.putObject(Matchers.any(classOf[PutObjectRequest]))).thenThrow(new AmazonServiceException("AWS failed"))
220
- Push.pushSite must equalTo(1)
220
+ Push.push must equalTo(1)
221
221
  }
222
222
 
223
- "be 1 if any of the redirects fails" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
223
+ "be 1 if any of the redirects fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
224
224
  config = """
225
225
  |redirects:
226
226
  | index.php: /index.html
227
227
  """.stripMargin
228
228
  when(amazonS3Client.putObject(Matchers.any(classOf[PutObjectRequest]))).thenThrow(new AmazonServiceException("AWS failed"))
229
- Push.pushSite must equalTo(1)
229
+ Push.push must equalTo(1)
230
230
  }
231
231
 
232
- "be 0 if CloudFront invalidations and uploads succeed"in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
232
+ "be 0 if CloudFront invalidations and uploads succeed"in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
233
233
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
234
234
  setLocalFile("test.css")
235
235
  setOutdatedS3Keys("test.css")
236
- Push.pushSite must equalTo(0)
236
+ Push.push must equalTo(0)
237
237
  }
238
238
 
239
- "be 1 if CloudFront is unreachable or broken"in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
239
+ "be 1 if CloudFront is unreachable or broken"in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
240
240
  setCloudFrontAsInternallyBroken()
241
241
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
242
242
  setLocalFile("test.css")
243
243
  setOutdatedS3Keys("test.css")
244
- Push.pushSite must equalTo(1)
244
+ Push.push must equalTo(1)
245
245
  }
246
246
 
247
- "be 0 if upload retry succeeds" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
247
+ "be 0 if upload retry succeeds" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
248
248
  setLocalFile("index.html")
249
249
  uploadFailsAndThenSucceeds(howManyFailures = 1)
250
- Push.pushSite must equalTo(0)
250
+ Push.push must equalTo(0)
251
251
  }
252
252
 
253
- "be 1 if delete retry fails" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
253
+ "be 1 if delete retry fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
254
254
  setLocalFile("index.html")
255
255
  uploadFailsAndThenSucceeds(howManyFailures = 6)
256
- Push.pushSite must equalTo(1)
256
+ Push.push must equalTo(1)
257
257
  }
258
258
 
259
- "be 1 if an object listing fails" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
259
+ "be 1 if an object listing fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
260
260
  setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
261
261
  objectListingFailsAndThenSucceeds(howManyFailures = 6)
262
- Push.pushSite must equalTo(1)
262
+ Push.push must equalTo(1)
263
263
  }
264
264
  }
265
265
 
266
266
  "s3_website.yml file" should {
267
- "never be uploaded" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
267
+ "never be uploaded" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
268
268
  setLocalFile("s3_website.yml")
269
- Push.pushSite
269
+ Push.push
270
270
  noUploadsOccurred must beTrue
271
271
  }
272
272
  }
273
273
 
274
274
  "exclude_from_upload: string" should {
275
- "result in matching files not being uploaded" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
275
+ "result in matching files not being uploaded" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
276
276
  config = "exclude_from_upload: .DS_.*?"
277
277
  setLocalFile(".DS_Store")
278
- Push.pushSite
278
+ Push.push
279
279
  noUploadsOccurred must beTrue
280
280
  }
281
281
  }
@@ -285,23 +285,23 @@ class S3WebsiteSpec extends Specification {
285
285
  - regex
286
286
  - another_exclusion
287
287
  """ should {
288
- "result in matching files not being uploaded" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
288
+ "result in matching files not being uploaded" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
289
289
  config = """
290
290
  |exclude_from_upload:
291
291
  | - .DS_.*?
292
292
  | - logs
293
293
  """.stripMargin
294
294
  setLocalFiles(".DS_Store", "logs/test.log")
295
- Push.pushSite
295
+ Push.push
296
296
  noUploadsOccurred must beTrue
297
297
  }
298
298
  }
299
299
 
300
300
  "ignore_on_server: value" should {
301
- "not delete the S3 objects that match the ignore value" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
301
+ "not delete the S3 objects that match the ignore value" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
302
302
  config = "ignore_on_server: logs"
303
303
  setS3Files(S3File("logs/log.txt", ""))
304
- Push.pushSite
304
+ Push.push
305
305
  noDeletesOccurred must beTrue
306
306
  }
307
307
  }
@@ -311,224 +311,228 @@ class S3WebsiteSpec extends Specification {
311
311
  - regex
312
312
  - another_ignore
313
313
  """ should {
314
- "not delete the S3 objects that match the ignore value" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
314
+ "not delete the S3 objects that match the ignore value" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
315
315
  config = """
316
316
  |ignore_on_server:
317
317
  | - .*txt
318
318
  """.stripMargin
319
319
  setS3Files(S3File("logs/log.txt", ""))
320
- Push.pushSite
320
+ Push.push
321
321
  noDeletesOccurred must beTrue
322
322
  }
323
323
  }
324
324
 
325
325
  "max-age in config" can {
326
- "be applied to all files" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
326
+ "be applied to all files" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
327
327
  config = "max_age: 60"
328
328
  setLocalFile("index.html")
329
- Push.pushSite
329
+ Push.push
330
330
  sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=60")
331
331
  }
332
332
 
333
- "be applied to files that match the glob" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
333
+ "be applied to files that match the glob" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
334
334
  config = """
335
335
  |max_age:
336
336
  | "*.html": 90
337
337
  """.stripMargin
338
338
  setLocalFile("index.html")
339
- Push.pushSite
339
+ Push.push
340
340
  sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=90")
341
341
  }
342
342
 
343
- "be applied to directories that match the glob" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
343
+ "be applied to directories that match the glob" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
344
344
  config = """
345
345
  |max_age:
346
346
  | "assets/**/*.js": 90
347
347
  """.stripMargin
348
348
  setLocalFile("assets/lib/jquery.js")
349
- Push.pushSite
349
+ Push.push
350
350
  sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=90")
351
351
  }
352
352
 
353
- "not be applied if the glob doesn't match" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
353
+ "not be applied if the glob doesn't match" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
354
354
  config = """
355
355
  |max_age:
356
356
  | "*.js": 90
357
357
  """.stripMargin
358
358
  setLocalFile("index.html")
359
- Push.pushSite
359
+ Push.push
360
360
  sentPutObjectRequest.getMetadata.getCacheControl must beNull
361
361
  }
362
362
 
363
- "be used to disable caching" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
363
+ "be used to disable caching" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
364
364
  config = "max_age: 0"
365
365
  setLocalFile("index.html")
366
- Push.pushSite
366
+ Push.push
367
367
  sentPutObjectRequest.getMetadata.getCacheControl must equalTo("no-cache; max-age=0")
368
368
  }
369
369
  }
370
370
 
371
371
  "max-age in config" should {
372
- "respect the more specific glob" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
372
+ "respect the more specific glob" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
373
373
  config = """
374
374
  |max_age:
375
375
  | "assets/*": 150
376
376
  | "assets/*.gif": 86400
377
377
  """.stripMargin
378
378
  setLocalFiles("assets/jquery.js", "assets/picture.gif")
379
- Push.pushSite
379
+ Push.push
380
380
  sentPutObjectRequests.find(_.getKey == "assets/jquery.js").get.getMetadata.getCacheControl must equalTo("max-age=150")
381
381
  sentPutObjectRequests.find(_.getKey == "assets/picture.gif").get.getMetadata.getCacheControl must equalTo("max-age=86400")
382
382
  }
383
383
  }
384
384
 
385
385
  "s3_reduced_redundancy: true in config" should {
386
- "result in uploads being marked with reduced redundancy" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
386
+ "result in uploads being marked with reduced redundancy" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
387
387
  config = "s3_reduced_redundancy: true"
388
388
  setLocalFile("file.exe")
389
- Push.pushSite
389
+ Push.push
390
390
  sentPutObjectRequest.getStorageClass must equalTo("REDUCED_REDUNDANCY")
391
391
  }
392
392
  }
393
393
 
394
394
  "s3_reduced_redundancy: false in config" should {
395
- "result in uploads being marked with the default storage class" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
395
+ "result in uploads being marked with the default storage class" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
396
396
  config = "s3_reduced_redundancy: false"
397
397
  setLocalFile("file.exe")
398
- Push.pushSite
398
+ Push.push
399
399
  sentPutObjectRequest.getStorageClass must beNull
400
400
  }
401
401
  }
402
402
 
403
403
  "redirect in config" should {
404
- "result in a redirect instruction that is sent to AWS" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
404
+ "result in a redirect instruction that is sent to AWS" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
405
405
  config = """
406
406
  |redirects:
407
407
  | index.php: /index.html
408
408
  """.stripMargin
409
- Push.pushSite
409
+ Push.push
410
410
  sentPutObjectRequest.getRedirectLocation must equalTo("/index.html")
411
411
  }
412
412
 
413
- "result in max-age=0 Cache-Control header on the object" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
413
+ "result in max-age=0 Cache-Control header on the object" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
414
414
  config = """
415
415
  |redirects:
416
416
  | index.php: /index.html
417
417
  """.stripMargin
418
- Push.pushSite
418
+ Push.push
419
419
  sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=0, no-cache")
420
420
  }
421
421
  }
422
422
 
423
423
  "redirect in config and an object on the S3 bucket" should {
424
- "not result in the S3 object being deleted" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
424
+ "not result in the S3 object being deleted" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
425
425
  config = """
426
426
  |redirects:
427
427
  | index.php: /index.html
428
428
  """.stripMargin
429
429
  setLocalFile("index.php")
430
430
  setS3Files(S3File("index.php", "md5"))
431
- Push.pushSite
431
+ Push.push
432
432
  noDeletesOccurred must beTrue
433
433
  }
434
434
  }
435
435
 
436
436
  "dotfiles" should {
437
- "be included in the pushed files" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
437
+ "be included in the pushed files" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
438
438
  setLocalFile(".vimrc")
439
- Push.pushSite
439
+ Push.push
440
440
  sentPutObjectRequest.getKey must equalTo(".vimrc")
441
441
  }
442
442
  }
443
443
 
444
444
  "content type inference" should {
445
- "add charset=utf-8 to all html documents" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
445
+ "add charset=utf-8 to all html documents" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
446
446
  setLocalFile("index.html")
447
- Push.pushSite
447
+ Push.push
448
448
  sentPutObjectRequest.getMetadata.getContentType must equalTo("text/html; charset=utf-8")
449
449
  }
450
450
 
451
- "add charset=utf-8 to all text documents" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
451
+ "add charset=utf-8 to all text documents" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
452
452
  setLocalFile("index.txt")
453
- Push.pushSite
453
+ Push.push
454
454
  sentPutObjectRequest.getMetadata.getContentType must equalTo("text/plain; charset=utf-8")
455
455
  }
456
456
 
457
- "add charset=utf-8 to all json documents" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
457
+ "add charset=utf-8 to all json documents" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
458
458
  setLocalFile("data.json")
459
- Push.pushSite
459
+ Push.push
460
460
  sentPutObjectRequest.getMetadata.getContentType must equalTo("application/json; charset=utf-8")
461
461
  }
462
462
 
463
- "resolve the content type from file contents" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
463
+ "resolve the content type from file contents" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
464
464
  setLocalFileWithContent(("index", "<html><body><h1>hi</h1></body></html>"))
465
- Push.pushSite
465
+ Push.push
466
466
  sentPutObjectRequest.getMetadata.getContentType must equalTo("text/html; charset=utf-8")
467
467
  }
468
468
  }
469
469
 
470
470
  "ERB in config file" should {
471
- "be evaluated" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
471
+ "be evaluated" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
472
472
  config = """
473
473
  |redirects:
474
474
  |<%= ('a'..'f').to_a.map do |t| ' '+t+ ': /'+t+'.html' end.join('\n')%>
475
475
  """.stripMargin
476
- Push.pushSite
476
+ Push.push
477
477
  sentPutObjectRequests.length must equalTo(6)
478
478
  sentPutObjectRequests.forall(_.getRedirectLocation != null) must beTrue
479
479
  }
480
480
  }
481
481
 
482
- "logging" should {
483
- "print the debug messages when --verbose is defined" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
484
- Push.pushSite
485
- logEntries must contain("[debg] Querying S3 files")
486
- }
487
-
488
- "not print the debug messages by default" in new EmptySite with NonVerboseLogger with MockAWS with DefaultRunMode {
489
- Push.pushSite
490
- logEntries.forall(_.contains("[debg]")) must beFalse
491
- }
492
- }
493
-
494
482
  "dry run" should {
495
- "not push updates" in new EmptySite with VerboseLogger with MockAWS with DryRunMode {
483
+ "not push updates" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
496
484
  setLocalFileWithContent(("index.html", "<div>new</div>"))
497
485
  setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
498
- Push.pushSite
486
+ Push.push
499
487
  noUploadsOccurred must beTrue
500
488
  }
501
489
 
502
- "not push redirects" in new EmptySite with VerboseLogger with MockAWS with DryRunMode {
490
+ "not push redirects" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
503
491
  config =
504
492
  """
505
493
  |redirects:
506
494
  | index.php: /index.html
507
495
  """.stripMargin
508
- Push.pushSite
496
+ Push.push
509
497
  noUploadsOccurred must beTrue
510
498
  }
511
499
 
512
- "not push deletes" in new EmptySite with VerboseLogger with MockAWS with DryRunMode {
500
+ "not push deletes" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
513
501
  setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
514
- Push.pushSite
502
+ Push.push
515
503
  noUploadsOccurred must beTrue
516
504
  }
517
505
 
518
- "not push new files" in new EmptySite with VerboseLogger with MockAWS with DryRunMode {
506
+ "not push new files" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
519
507
  setLocalFile("index.html")
520
- Push.pushSite
508
+ Push.push
521
509
  noUploadsOccurred must beTrue
522
510
  }
523
511
 
524
- "not invalidate files" in new EmptySite with VerboseLogger with MockAWS with DryRunMode {
512
+ "not invalidate files" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
525
513
  config = "cloudfront_invalidation_id: AABBCC"
526
514
  setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
527
- Push.pushSite
515
+ Push.push
528
516
  noInvalidationsOccurred must beTrue
529
517
  }
530
518
  }
531
519
 
520
+ "Jekyll site" should {
521
+ "be detected automatically" in new JekyllSite with EmptySite with MockAWS with DefaultRunMode {
522
+ setLocalFile("index.html")
523
+ Push.push
524
+ sentPutObjectRequests.length must equalTo(1)
525
+ }
526
+ }
527
+
528
+ "Nanoc site" should {
529
+ "be detected automatically" in new NanocSite with EmptySite with MockAWS with DefaultRunMode {
530
+ setLocalFile("index.html")
531
+ Push.push
532
+ sentPutObjectRequests.length must equalTo(1)
533
+ }
534
+ }
535
+
532
536
  trait DefaultRunMode {
533
537
  implicit def pushMode: PushMode = new PushMode {
534
538
  def dryRun = false
@@ -542,23 +546,6 @@ class S3WebsiteSpec extends Specification {
542
546
  }
543
547
 
544
548
  trait MockAWS extends MockS3 with MockCloudFront with Scope
545
-
546
- trait VerboseLogger extends LogCapturer {
547
- implicit val logger: Logger = new Logger(verboseOutput = true, logMessage = captureAndPrint)
548
- }
549
-
550
- trait NonVerboseLogger extends LogCapturer {
551
- implicit val logger: Logger = new Logger(verboseOutput = false, logMessage = captureAndPrint)
552
- }
553
-
554
- trait LogCapturer {
555
- val logEntries: mutable.Buffer[String] = mutable.Buffer()
556
-
557
- def captureAndPrint(msg: String) {
558
- logEntries += msg.replaceAll("\u001B\\[[;\\d]*m", "") // Remove ANSI coloring
559
- println(msg)
560
- }
561
- }
562
549
 
563
550
  trait MockCloudFront extends MockAWSHelper {
564
551
  val amazonCloudFrontClient = mock(classOf[AmazonCloudFront])
@@ -599,8 +586,6 @@ class S3WebsiteSpec extends Specification {
599
586
  }).when(amazonCloudFrontClient).createInvalidation(Matchers.anyObject())
600
587
  }
601
588
 
602
-
603
-
604
589
  def setCloudFrontAsInternallyBroken() {
605
590
  when(amazonCloudFrontClient.createInvalidation(Matchers.anyObject())).thenThrow(new AmazonServiceException("CloudFront is down"))
606
591
  }
@@ -699,16 +684,40 @@ class S3WebsiteSpec extends Specification {
699
684
  }
700
685
  }
701
686
 
702
- trait SiteDirectory extends After {
703
- val siteDir = new File(FileUtils.getTempDirectory, "site" + Random.nextLong())
704
- siteDir.mkdir()
687
+ trait Directories extends BeforeAfter {
688
+ implicit final val workingDirectory: File = new File(FileUtils.getTempDirectory, "s3_website_dir" + Random.nextLong())
689
+ val siteDirectory: File // Represents the --site=X option
690
+ val configDirectory: File = workingDirectory // Represents the --config-dir=X option
691
+
692
+ lazy val allDirectories = workingDirectory :: siteDirectory :: configDirectory :: Nil
693
+
694
+ def before {
695
+ allDirectories foreach forceMkdir
696
+ }
705
697
 
706
698
  def after {
707
- FileUtils.forceDelete(siteDir)
699
+ allDirectories foreach { dir =>
700
+ if (dir.exists) forceDelete(dir)
701
+ }
708
702
  }
709
703
  }
704
+
705
+ /**
706
+ * Represents the situation where the current working directory, site dir and config dir are in the same directory
707
+ */
708
+ trait AllInSameDirectory extends Directories {
709
+ val siteDirectory = workingDirectory
710
+ }
711
+
712
+ trait JekyllSite extends Directories {
713
+ val siteDirectory = new File(workingDirectory, "_site")
714
+ }
715
+
716
+ trait NanocSite extends Directories {
717
+ val siteDirectory = new File(workingDirectory, "public/output")
718
+ }
710
719
 
711
- trait EmptySite extends SiteDirectory {
720
+ trait EmptySite extends Directories {
712
721
  type LocalFileWithContent = (String, String)
713
722
 
714
723
  val localFilesWithContent: mutable.Buffer[LocalFileWithContent] = mutable.Buffer()
@@ -717,15 +726,15 @@ class S3WebsiteSpec extends Specification {
717
726
  def setLocalFileWithContent(fileNameAndContent: LocalFileWithContent) = localFilesWithContent += fileNameAndContent
718
727
  def setLocalFilesWithContent(fileNamesAndContent: LocalFileWithContent*) = fileNamesAndContent foreach setLocalFileWithContent
719
728
  var config = ""
720
- var baseConfig =
729
+ val baseConfig =
721
730
  """
722
731
  |s3_id: foo
723
732
  |s3_secret: bar
724
733
  |s3_bucket: bucket
725
734
  """.stripMargin
726
735
 
727
- implicit lazy val testSite: Site = siteWithFilesAndContent(config, localFilesWithContent)
728
- implicit def logger: Logger
736
+ implicit lazy val cliArgs: CliArgs = siteWithFilesAndContent(config, localFilesWithContent)
737
+ def pushMode: PushMode // Represents the --dry-run switch
729
738
 
730
739
  def buildSite(
731
740
  config: String = "",
@@ -735,24 +744,30 @@ class S3WebsiteSpec extends Specification {
735
744
  |s3_secret: bar
736
745
  |s3_bucket: bucket
737
746
  """.stripMargin
738
- ): Site = {
739
- val configFile = new File(siteDir, "s3_website.yml")
747
+ ): CliArgs = {
748
+ val configFile = new File(configDirectory, "s3_website.yml")
740
749
  write(configFile,
741
750
  s"""
742
751
  |$baseConfig
743
752
  |$config
744
753
  """.stripMargin
745
754
  )
746
- val errorOrSite: Either[ErrorReport, Site] = Site.loadSite(configFile.getAbsolutePath, siteDir.getAbsolutePath)(logger)
747
- errorOrSite.left.foreach (error => throw new RuntimeException(error.reportMessage))
748
- errorOrSite.right.get
755
+ new CliArgs {
756
+ override def verbose = true
757
+
758
+ override def dryRun = pushMode.dryRun
759
+
760
+ override def site = siteDirectory.getAbsolutePath
761
+
762
+ override def configDir = configDirectory.getAbsolutePath
763
+ }
749
764
  }
750
765
 
751
- def siteWithFilesAndContent(config: String = "", localFilesWithContent: Seq[LocalFileWithContent]): Site = {
766
+ def siteWithFilesAndContent(config: String = "", localFilesWithContent: Seq[LocalFileWithContent]): CliArgs = {
752
767
  localFilesWithContent.foreach {
753
768
  case (filePath, content) =>
754
- val file = new File(siteDir, filePath)
755
- FileUtils.forceMkdir(file.getParentFile)
769
+ val file = new File(siteDirectory, filePath)
770
+ forceMkdir(file.getParentFile)
756
771
  file.createNewFile()
757
772
  write(file, content)
758
773
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: s3_website_monadic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.29
4
+ version: 0.0.30
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lauri Lehmijoki
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-12 00:00:00.000000000 Z
11
+ date: 2014-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -102,9 +102,6 @@ files:
102
102
  - build.sbt
103
103
  - changelog.md
104
104
  - lib/s3_website.rb
105
- - lib/s3_website/jekyll.rb
106
- - lib/s3_website/nanoc.rb
107
- - lib/s3_website/paths.rb
108
105
  - lib/s3_website/version.rb
109
106
  - project/assembly.sbt
110
107
  - project/build.properties
@@ -124,6 +121,7 @@ files:
124
121
  - src/main/scala/s3/website/model/Site.scala
125
122
  - src/main/scala/s3/website/model/errors.scala
126
123
  - src/main/scala/s3/website/model/push.scala
124
+ - src/main/scala/s3/website/model/ssg.scala
127
125
  - src/main/scala/s3/website/package.scala
128
126
  - src/test/scala/s3/website/S3WebsiteSpec.scala
129
127
  homepage: https://github.com/laurilehmijoki/s3_website/tree/s3_website_monadic
@@ -1,5 +0,0 @@
1
- module S3Website
2
- module Jekyll
3
- SITE_PATH = '_site'
4
- end
5
- end
@@ -1,5 +0,0 @@
1
- module S3Website
2
- module Nanoc
3
- SITE_PATH = 'public/output'
4
- end
5
- end
@@ -1,40 +0,0 @@
1
- module S3Website
2
- class Paths
3
- def self.site_paths
4
- [Nanoc::SITE_PATH, Jekyll::SITE_PATH]
5
- end
6
-
7
- def self.infer_site_path(candidate_path, pwd)
8
- if candidate_path == 'infer automatically'
9
- infer_automatically pwd
10
- else
11
- candidate_path_if_exists candidate_path
12
- end
13
- end
14
-
15
- private
16
-
17
- def self.candidate_path_if_exists(candidate_path)
18
- raise NoWebsiteDirectoryFound.new(
19
- "Can't find a website in " + candidate_path
20
- ) unless File.exists? candidate_path
21
- candidate_path
22
- end
23
-
24
- def self.infer_automatically(pwd)
25
- site_path = site_paths.
26
- map do |site_path|
27
- pwd + '/' + site_path
28
- end.
29
- find do |site_path|
30
- File.exists? site_path
31
- end
32
- if site_path
33
- site_path
34
- else
35
- puts "Could not find a website directory. Specify one with the --site parameter."
36
- exit
37
- end
38
- end
39
- end
40
- end