s3_website 2.1.16 → 2.2.0

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: ed15b1ca2ef35ae9db46ac7fb1fe4551ab935457
4
- data.tar.gz: 2dd43e7bcf6708f25e6ebd178cf91d96777cf244
3
+ metadata.gz: a0db818cde92ba72ae2bca269f676c18e63277ac
4
+ data.tar.gz: 20315d8fde0ef16cad751cf104f45eea931612d0
5
5
  SHA512:
6
- metadata.gz: 9e94858d9c5de224370e6b91ec88f3eefa62e5662e084ff0ab3e3d8de9bc7162017f050ea7cd00d899265bec303f3d8875f4ad9d78a5bc8f78dde128a5e0dcaf
7
- data.tar.gz: 99614f6a9649063480a9227a06429ed602eecc6bcedfbaa7eb0b695686a823bbc7c663a4298354a3f2fb4be438ed46359810d75722acbf0642d104d286f042df
6
+ metadata.gz: 0229a6d300c77da0ea0e01d151994c26fd7cd62fd84e64fdf6c58e7b1ee087b5a6f29bd8667ec7432efd3a6dfbf058facfb07aa90e764840be78c32aeb2c4fef
7
+ data.tar.gz: 03fd97df2738bd7a8e0b09f37e759d7de45b3075932bf828cdda04eae2667eb7f547b8563a4bb2b80e2d4c8a1acbbd36c892bc0fd4b9f8522dbd013febcc0abe
data/README.md CHANGED
@@ -33,23 +33,19 @@ Here's how you can get started:
33
33
  S3 website. If the bucket does not exist, the command will create it for you.
34
34
  * Run `s3_website push` to push your website to S3. Congratulations! You are live.
35
35
 
36
- ### For Jekyll users
36
+ ### Specifying the location of your website
37
37
 
38
- S3_website will automatically discover your website in the *_site* directory.
38
+ S3_website will automatically discover websites in the *_site* and
39
+ *public/output* directories.
39
40
 
40
- ### For Nanoc users
41
+ If your website is not in either of those directories, you can
42
+ point the location of your website in two ways:
41
43
 
42
- S3_website will automatically discover your website in the *public/output* directory.
43
-
44
- ### For others
45
-
46
- It's a good idea to store the `s3_website.yml` file in your project's root.
47
- Let's say the contents you wish to upload to your S3 website bucket are in
48
- *my_website_output*. You can upload the contents of that directory with
49
- `s3_website push --site my_website_output`.
44
+ 1. Add the line `site: path-to-your-website` into the `s3_website.yml` file
45
+ 2. Or, use the `--site=path-to-your-site` command-line argument
50
46
 
51
47
  If you want to store the `s3_website.yml` file in a directory other than
52
- the project's root you can specify the directory.
48
+ the project's root you can specify the directory like so:
53
49
  `s3_website push --config-dir config`.
54
50
 
55
51
  ### Using environment variables
data/bin/s3_website CHANGED
@@ -149,8 +149,6 @@ def autoinstall_java_or_print_help_and_exit(logger)
149
149
  exit 1
150
150
  end
151
151
 
152
- manual_method_help =
153
-
154
152
  automatic_method = automatic_methods.find { |automatic_method|
155
153
  resolve_exit_status automatic_method.fetch(:package_manager_lookup)
156
154
  }
data/build.sbt CHANGED
@@ -4,7 +4,7 @@ name := "s3_website"
4
4
 
5
5
  version := "0.0.1"
6
6
 
7
- scalaVersion := "2.11.1"
7
+ scalaVersion := "2.11.2"
8
8
 
9
9
  scalacOptions += "-feature"
10
10
 
@@ -14,8 +14,6 @@ scalacOptions += "-language:postfixOps"
14
14
 
15
15
  scalacOptions += "-target:jvm-1.6"
16
16
 
17
- javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6")
18
-
19
17
  libraryDependencies += "org.yaml" % "snakeyaml" % "1.13"
20
18
 
21
19
  libraryDependencies += "org.jruby" % "jruby" % "1.7.11"
data/changelog.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  This project uses [Semantic Versioning](http://semver.org).
4
4
 
5
+ ## 2.2.0
6
+
7
+ * Specify the location of the website in the *s3_website.yml* file
8
+
9
+ Just add the setting `site: path-to-your-website` into the file.
10
+
11
+ * Fix Nanoc auto detection
12
+
13
+ Previously, a website in *public/output* was not automatically detected.
14
+
5
15
  ## 2.1.16
6
16
 
7
17
  * Support non-US-ASCII files when using `ignore_on_server`
@@ -1,3 +1,3 @@
1
1
  module S3Website
2
- VERSION = '2.1.16'
2
+ VERSION = '2.2.0'
3
3
  end
@@ -5,6 +5,8 @@ s3_bucket: your.blog.bucket.com
5
5
  # Below are examples of all the available configurations.
6
6
  # See README for more detailed info on each of them.
7
7
 
8
+ # site: path-to-your-website
9
+
8
10
  # max_age:
9
11
  # "assets/*": 6000
10
12
  # "*": 300
@@ -0,0 +1,18 @@
1
+ package s3.website
2
+
3
+ object ByteHelper {
4
+
5
+ // Adapted from http://stackoverflow.com/a/3758880/219947
6
+ def humanReadableByteCount(bytes: Long): String = {
7
+ val si: Boolean = true
8
+ val unit: Int = if (si) 1000 else 1024
9
+ if (bytes < unit) {
10
+ bytes + " B"
11
+ } else {
12
+ val exp: Int = (Math.log(bytes) / Math.log(unit)).asInstanceOf[Int]
13
+ val pre: String = (if (si) "kMGTPE" else "KMGTPE").charAt(exp - 1) + (if (si) "" else "i")
14
+ val formatArgs = (bytes / Math.pow(unit, exp)).asInstanceOf[AnyRef] :: pre :: Nil
15
+ String.format("%.1f %sB", formatArgs.toArray:_*)
16
+ }
17
+ }
18
+ }
@@ -1,5 +1,6 @@
1
1
  package s3.website
2
2
 
3
+ import s3.website.model.Config.S3_website_yml
3
4
  import s3.website.model.Site._
4
5
  import scala.concurrent.{ExecutionContextExecutor, Future, Await}
5
6
  import scala.concurrent.duration._
@@ -21,7 +22,6 @@ import s3.website.S3.S3Setting
21
22
  import s3.website.CloudFront.CloudFrontSetting
22
23
  import s3.website.S3.SuccessfulUpload
23
24
  import s3.website.CloudFront.FailedInvalidation
24
- import scala.Int
25
25
  import java.io.File
26
26
  import com.lexicalscope.jewel.cli.CliFactory.parseArguments
27
27
  import s3.website.ByteHelper.humanReadableByteCount
@@ -52,15 +52,11 @@ object Push {
52
52
  def dryRun = cliArgs.dryRun
53
53
  }
54
54
 
55
- val errorOrSiteDir: ErrorOrFile =
56
- Option(cliArgs.site).fold(Ssg.findSiteDirectory(workingDirectory))(siteDirFromCli => Right(new File(siteDirFromCli)))
57
- def errorOrSite(siteInDirectory: File): Either[ErrorReport, Site] =
58
- loadSite(Option(cliArgs.configDir).getOrElse(workingDirectory.getPath) + "/s3_website.yml", siteInDirectory.getAbsolutePath)
55
+ implicit val yamlConfig = S3_website_yml(new File(Option(cliArgs.configDir).getOrElse(workingDirectory.getPath) + "/s3_website.yml"))
59
56
 
60
- val errorOrPushStatus = for {
61
- siteInDirectory <- errorOrSiteDir.right
62
- loadedSite <- errorOrSite(siteInDirectory).right
63
- } yield {
57
+ val errorOrPushStatus = for (
58
+ loadedSite <- loadSite.right
59
+ ) yield {
64
60
  implicit val site = loadedSite
65
61
  val threadPool = newFixedThreadPool(site.config.concurrency_level)
66
62
  implicit val executor = fromExecutor(threadPool)
@@ -1,5 +1,7 @@
1
1
  package s3.website.model
2
2
 
3
+ import java.io.File
4
+
3
5
  import scala.util.{Failure, Try}
4
6
  import scala.collection.JavaConversions._
5
7
  import s3.website.Ruby.rubyRuntime
@@ -11,6 +13,7 @@ case class Config(
11
13
  s3_secret: Option[String], // If undefined, use IAM Roles (http://docs.aws.amazon.com/AWSSdkDocsJava/latest/DeveloperGuide/java-dg-roles.html)
12
14
  s3_bucket: String,
13
15
  s3_endpoint: S3Endpoint,
16
+ site: Option[String],
14
17
  max_age: Option[Either[Int, Map[String, Int]]],
15
18
  gzip: Option[Either[Boolean, Seq[String]]],
16
19
  gzip_zopfli: Option[Boolean],
@@ -154,7 +157,7 @@ object Config {
154
157
  unsafeYaml.yamlObject.asInstanceOf[java.util.Map[String, _]].toMap get key
155
158
  }
156
159
 
157
- def erbEval(erbString: String, yamlConfigPath: String): Try[String] = Try {
160
+ def erbEval(erbString: String, yamlConfig: S3_website_yml): Try[String] = Try {
158
161
  val erbStringWithoutComments = erbString.replaceAll("^\\s*#.*", "")
159
162
  rubyRuntime.evalScriptlet(
160
163
  s"""|# encoding: utf-8
@@ -167,9 +170,13 @@ object Config {
167
170
  """.stripMargin
168
171
  ).asJavaString()
169
172
  } match {
170
- case Failure(err) => Failure(new RuntimeException(s"Failed to parse ERB in $yamlConfigPath:\n${err.getMessage}"))
173
+ case Failure(err) => Failure(new RuntimeException(s"Failed to parse ERB in $yamlConfig:\n${err.getMessage}"))
171
174
  case x => x
172
175
  }
173
176
 
174
177
  case class UnsafeYaml(yamlObject: AnyRef)
178
+
179
+ case class S3_website_yml(file: File) {
180
+ override def toString = file.getPath
181
+ }
175
182
  }
@@ -1,6 +1,9 @@
1
1
  package s3.website.model
2
2
 
3
3
  import java.io.File
4
+ import s3.website.Push.CliArgs
5
+ import s3.website.model.Ssg.autodetectSiteDir
6
+
4
7
  import scala.util.Try
5
8
  import org.yaml.snakeyaml.Yaml
6
9
  import s3.website.model.Config._
@@ -11,8 +14,8 @@ import scala.util.Failure
11
14
  import s3.website.model.Config.UnsafeYaml
12
15
  import scala.util.Success
13
16
 
14
- case class Site(rootDirectory: String, config: Config) {
15
- def resolveS3Key(file: File) = file.getAbsolutePath.replace(rootDirectory, "").replaceFirst("^/", "")
17
+ case class Site(rootDirectory: File, config: Config) {
18
+ def resolveS3Key(file: File) = file.getAbsolutePath.replace(rootDirectory.getAbsolutePath, "").replaceFirst("^/", "")
16
19
 
17
20
  def resolveFile(s3File: S3File): File = resolveFile(s3File.s3Key)
18
21
 
@@ -20,21 +23,22 @@ case class Site(rootDirectory: String, config: Config) {
20
23
  }
21
24
 
22
25
  object Site {
23
- def loadSite(yamlConfigPath: String, siteRootDirectory: String)
24
- (implicit logger: Logger): Either[ErrorReport, Site] = {
26
+ def parseConfig(implicit logger: Logger, yamlConfig: S3_website_yml): Either[ErrorReport, Config] = {
25
27
  val yamlObjectTry = for {
26
- yamlString <- Try(fromFile(new File(yamlConfigPath)).mkString)
27
- yamlWithErbEvaluated <- erbEval(yamlString, yamlConfigPath)
28
+ yamlString <- Try(fromFile(yamlConfig.file).mkString)
29
+ yamlWithErbEvaluated <- erbEval(yamlString, yamlConfig)
28
30
  yamlObject <- Try(new Yaml() load yamlWithErbEvaluated)
29
31
  } yield yamlObject
32
+
30
33
  yamlObjectTry match {
31
34
  case Success(yamlObject) =>
32
35
  implicit val unsafeYaml = UnsafeYaml(yamlObject)
33
- val config: Either[ErrorReport, Config] = for {
36
+ for {
34
37
  s3_id <- loadOptionalString("s3_id").right
35
38
  s3_secret <- loadOptionalString("s3_secret").right
36
39
  s3_bucket <- loadRequiredString("s3_bucket").right
37
40
  s3_endpoint <- loadEndpoint.right
41
+ site <- loadOptionalString("site").right
38
42
  max_age <- loadMaxAge.right
39
43
  gzip <- loadOptionalBooleanOrStringSeq("gzip").right
40
44
  gzip_zopfli <- loadOptionalBoolean("gzip_zopfli").right
@@ -49,16 +53,17 @@ object Site {
49
53
  } yield {
50
54
  gzip_zopfli.foreach(_ => logger.info(
51
55
  """|Zopfli is not currently supported. Falling back to regular gzip.
52
- |If you find a stable Java implementation for zopfli, please send an email to lauri.lehmijoki@iki.fi about it."""
53
- .stripMargin))
56
+ |If you find a stable Java implementation for zopfli, please send an email to lauri.lehmijoki@iki.fi about it."""
57
+ .stripMargin))
54
58
  extensionless_mime_type.foreach(_ => logger.info(
55
- s"Ignoring the extensionless_mime_type setting in $yamlConfigPath. Counting on Apache Tika to infer correct mime types.")
59
+ s"Ignoring the extensionless_mime_type setting in $yamlConfig. Counting on Apache Tika to infer correct mime types.")
56
60
  )
57
61
  Config(
58
62
  s3_id,
59
63
  s3_secret,
60
64
  s3_bucket,
61
65
  s3_endpoint getOrElse S3Endpoint.defaultEndpoint,
66
+ site,
62
67
  max_age,
63
68
  gzip,
64
69
  gzip_zopfli,
@@ -71,10 +76,38 @@ object Site {
71
76
  concurrency_level.fold(20)(_ max 20) // At minimum, run 20 concurrent operations
72
77
  )
73
78
  }
74
-
75
- config.right.map(Site(siteRootDirectory, _))
76
79
  case Failure(error) =>
77
80
  Left(ErrorReport(error))
78
81
  }
79
82
  }
83
+
84
+ def loadSite(implicit yamlConfig: S3_website_yml, cliArgs: CliArgs, workingDirectory: File, logger: Logger): Either[ErrorReport, Site] =
85
+ parseConfig.right.flatMap { cfg =>
86
+ implicit val config: Config = cfg
87
+ val errorOrSiteDir = resolveSiteDir.fold(Left(ErrorReport(noSiteFound)): Either[ErrorReport, File])(Right(_))
88
+ errorOrSiteDir.right.map(Site(_, config))
89
+ }
90
+
91
+ val noSiteFound =
92
+ """|Could not find a website.
93
+ |Either use the --site=DIR command-line argument or define the location of the site in s3_website.yml.
94
+ |
95
+ |Here's an example of how you can define the site directory in s3_website.yml:
96
+ | site: dist/website""".stripMargin
97
+
98
+ def resolveSiteDir(implicit yamlConfig: S3_website_yml, config: Config, cliArgs: CliArgs, workingDirectory: File): Option[File] = {
99
+ val siteFromAutoDetect = autodetectSiteDir(workingDirectory)
100
+ val siteFromCliArgs = Option(cliArgs.site).map(new File(_))
101
+
102
+ siteFromCliArgs orElse siteFromConfig orElse siteFromAutoDetect
103
+ }
104
+
105
+ def siteFromConfig(implicit yamlConfig: S3_website_yml, config: Config, workingDirectory: File): Option[File] =
106
+ config
107
+ .site
108
+ .map(new File(_))
109
+ .map { siteDir =>
110
+ if (siteDir.isAbsolute) siteDir
111
+ else new File(yamlConfig.file.getParentFile, siteDir.getPath)
112
+ }
80
113
  }
@@ -142,7 +142,7 @@ object Files {
142
142
  if (doNotUpload) logger.debug(s"Excluded $s3Key from upload")
143
143
  doNotUpload
144
144
  }
145
- recursiveListFiles(new File(site.rootDirectory))
145
+ recursiveListFiles(site.rootDirectory)
146
146
  .filterNot(_.isDirectory)
147
147
  .filterNot(f => excludeFromUpload(site.resolveS3Key(f)))
148
148
  }
@@ -1,6 +1,7 @@
1
1
  package s3.website.model
2
2
 
3
3
  import java.io.File
4
+ import s3.website.model.Files.recursiveListFiles
4
5
  import s3.website.{ErrorOrFile, ErrorReport}
5
6
 
6
7
  // ssg = static site generator
@@ -11,17 +12,10 @@ trait Ssg {
11
12
  object Ssg {
12
13
  val automaticallySupportedSiteGenerators = Jekyll :: Nanoc :: Nil
13
14
 
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
15
+ def autodetectSiteDir(workingDirectory: File): Option[File] =
16
+ recursiveListFiles(workingDirectory).find { file =>
17
+ file.isDirectory && automaticallySupportedSiteGenerators.exists(ssg => file.getAbsolutePath.endsWith(ssg.outputDirectory))
19
18
  }
20
-
21
- def findSiteDirectory(workingDirectory: File): ErrorOrFile =
22
- Files.recursiveListFiles(workingDirectory).find { file =>
23
- file.isDirectory && automaticallySupportedSiteGenerators.exists(_.outputDirectory == file.getName)
24
- }.fold(Left(notFoundErrorReport): ErrorOrFile)(Right(_))
25
19
  }
26
20
 
27
21
  case object Jekyll extends Ssg {
@@ -1,40 +1,37 @@
1
1
  package s3.website
2
2
 
3
- import org.specs2.mutable.{BeforeAfter, After, Specification}
4
- import s3.website.model._
5
- import org.specs2.specification.Scope
6
- import org.apache.commons.io.FileUtils
7
3
  import java.io.File
8
- import scala.util.Random
9
- import org.mockito.{Mockito, Matchers, ArgumentCaptor}
10
- import org.mockito.Mockito._
4
+ import java.util.concurrent.atomic.AtomicInteger
5
+
6
+ import com.amazonaws.AmazonServiceException
7
+ import com.amazonaws.services.cloudfront.AmazonCloudFront
8
+ import com.amazonaws.services.cloudfront.model.{CreateInvalidationRequest, CreateInvalidationResult, TooManyInvalidationsInProgressException}
11
9
  import com.amazonaws.services.s3.AmazonS3
12
10
  import com.amazonaws.services.s3.model._
13
- import scala.concurrent.duration._
14
- import s3.website.S3.S3Setting
15
- import scala.collection.JavaConversions._
16
- import com.amazonaws.AmazonServiceException
17
11
  import org.apache.commons.codec.digest.DigestUtils._
18
- import s3.website.CloudFront.CloudFrontSetting
19
- import com.amazonaws.services.cloudfront.AmazonCloudFront
20
- import com.amazonaws.services.cloudfront.model.{CreateInvalidationResult, CreateInvalidationRequest, TooManyInvalidationsInProgressException}
21
- import org.mockito.stubbing.Answer
22
- import org.mockito.invocation.InvocationOnMock
23
- import java.util.concurrent.atomic.AtomicInteger
12
+ import org.apache.commons.io.FileUtils
24
13
  import org.apache.commons.io.FileUtils._
25
- import scala.collection.mutable
26
- import s3.website.Push.{push, CliArgs}
27
- import s3.website.CloudFront.CloudFrontSetting
28
- import s3.website.S3.S3Setting
29
- import org.apache.commons.codec.digest.DigestUtils
30
- import java.util.Date
14
+ import org.mockito.Mockito._
15
+ import org.mockito.invocation.InvocationOnMock
16
+ import org.mockito.stubbing.Answer
17
+ import org.mockito.{ArgumentCaptor, Matchers, Mockito}
18
+ import org.specs2.mutable.{BeforeAfter, Specification}
19
+ import org.specs2.specification.Scope
31
20
  import s3.website.CloudFront.CloudFrontSetting
21
+ import s3.website.Push.{CliArgs}
32
22
  import s3.website.S3.S3Setting
23
+ import s3.website.model.Config.S3_website_yml
24
+ import s3.website.model.Ssg.automaticallySupportedSiteGenerators
25
+ import s3.website.model._
26
+
27
+ import scala.collection.JavaConversions._
28
+ import scala.concurrent.duration._
29
+ import scala.util.Random
33
30
 
34
31
  class S3WebsiteSpec extends Specification {
35
32
 
36
33
  "gzip: true" should {
37
- "update a gzipped S3 object if the contents has changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
34
+ "update a gzipped S3 object if the contents has changed" in new BasicSetup {
38
35
  config = "gzip: true"
39
36
  setLocalFileWithContent(("styles.css", "<h1>hi again</h1>"))
40
37
  setS3Files(S3File("styles.css", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */))
@@ -42,7 +39,7 @@ class S3WebsiteSpec extends Specification {
42
39
  sentPutObjectRequest.getKey must equalTo("styles.css")
43
40
  }
44
41
 
45
- "not update a gzipped S3 object if the contents has not changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
42
+ "not update a gzipped S3 object if the contents has not changed" in new BasicSetup {
46
43
  config = "gzip: true"
47
44
  setLocalFileWithContent(("styles.css", "<h1>hi</h1>"))
48
45
  setS3Files(S3File("styles.css", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */))
@@ -55,7 +52,7 @@ class S3WebsiteSpec extends Specification {
55
52
  gzip:
56
53
  - .xml
57
54
  """ should {
58
- "update a gzipped S3 object if the contents has changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
55
+ "update a gzipped S3 object if the contents has changed" in new BasicSetup {
59
56
  config = """
60
57
  |gzip:
61
58
  | - .xml
@@ -68,40 +65,40 @@ class S3WebsiteSpec extends Specification {
68
65
  }
69
66
 
70
67
  "push" should {
71
- "not upload a file if it has not changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
68
+ "not upload a file if it has not changed" in new BasicSetup {
72
69
  setLocalFileWithContent(("index.html", "<div>hello</div>"))
73
70
  setS3Files(S3File("index.html", md5Hex("<div>hello</div>")))
74
71
  push
75
72
  noUploadsOccurred must beTrue
76
73
  }
77
74
 
78
- "update a file if it has changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
75
+ "update a file if it has changed" in new BasicSetup {
79
76
  setLocalFileWithContent(("index.html", "<h1>old text</h1>"))
80
77
  setS3Files(S3File("index.html", md5Hex("<h1>new text</h1>")))
81
78
  push
82
79
  sentPutObjectRequest.getKey must equalTo("index.html")
83
80
  }
84
81
 
85
- "create a file if does not exist on S3" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
82
+ "create a file if does not exist on S3" in new BasicSetup {
86
83
  setLocalFile("index.html")
87
84
  push
88
85
  sentPutObjectRequest.getKey must equalTo("index.html")
89
86
  }
90
87
 
91
- "delete files that are on S3 but not on local file system" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
88
+ "delete files that are on S3 but not on local file system" in new BasicSetup {
92
89
  setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
93
90
  push
94
91
  sentDelete must equalTo("old.html")
95
92
  }
96
93
 
97
- "try again if the upload fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
94
+ "try again if the upload fails" in new BasicSetup {
98
95
  setLocalFile("index.html")
99
96
  uploadFailsAndThenSucceeds(howManyFailures = 5)
100
97
  push
101
98
  verify(amazonS3Client, times(6)).putObject(Matchers.any(classOf[PutObjectRequest]))
102
99
  }
103
100
 
104
- "not try again if the upload fails on because of invalid credentials" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
101
+ "not try again if the upload fails on because of invalid credentials" in new BasicSetup {
105
102
  setLocalFile("index.html")
106
103
  when(amazonS3Client.putObject(Matchers.any(classOf[PutObjectRequest]))).thenThrow {
107
104
  val e = new AmazonServiceException("your credentials are incorrect")
@@ -112,7 +109,7 @@ class S3WebsiteSpec extends Specification {
112
109
  verify(amazonS3Client, times(1)).putObject(Matchers.any(classOf[PutObjectRequest]))
113
110
  }
114
111
 
115
- "try again if the request times out" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
112
+ "try again if the request times out" in new BasicSetup {
116
113
  var attempt = 0
117
114
  when(amazonS3Client putObject Matchers.any(classOf[PutObjectRequest])) thenAnswer new Answer[PutObjectResult] {
118
115
  def answer(invocation: InvocationOnMock) = {
@@ -132,14 +129,14 @@ class S3WebsiteSpec extends Specification {
132
129
  verify(amazonS3Client, times(2)).putObject(Matchers.any(classOf[PutObjectRequest]))
133
130
  }
134
131
 
135
- "try again if the delete fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
132
+ "try again if the delete fails" in new BasicSetup {
136
133
  setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
137
134
  deleteFailsAndThenSucceeds(howManyFailures = 5)
138
135
  push
139
136
  verify(amazonS3Client, times(6)).deleteObject(Matchers.anyString(), Matchers.anyString())
140
137
  }
141
138
 
142
- "try again if the object listing fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
139
+ "try again if the object listing fails" in new BasicSetup {
143
140
  setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
144
141
  objectListingFailsAndThenSucceeds(howManyFailures = 5)
145
142
  push
@@ -148,7 +145,7 @@ class S3WebsiteSpec extends Specification {
148
145
  }
149
146
 
150
147
  "push with CloudFront" should {
151
- "invalidate the updated CloudFront items" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
148
+ "invalidate the updated CloudFront items" in new BasicSetup {
152
149
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
153
150
  setLocalFiles("css/test.css", "articles/index.html")
154
151
  setOutdatedS3Keys("css/test.css", "articles/index.html")
@@ -156,14 +153,14 @@ class S3WebsiteSpec extends Specification {
156
153
  sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/css/test.css" :: "/articles/index.html" :: Nil).sorted)
157
154
  }
158
155
 
159
- "not send CloudFront invalidation requests on new objects" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
156
+ "not send CloudFront invalidation requests on new objects" in new BasicSetup {
160
157
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
161
158
  setLocalFile("newfile.js")
162
159
  push
163
160
  noInvalidationsOccurred must beTrue
164
161
  }
165
162
 
166
- "not send CloudFront invalidation requests on redirect objects" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
163
+ "not send CloudFront invalidation requests on redirect objects" in new BasicSetup {
167
164
  config = """
168
165
  |cloudfront_distribution_id: EGM1J2JJX9Z
169
166
  |redirects:
@@ -173,7 +170,7 @@ class S3WebsiteSpec extends Specification {
173
170
  noInvalidationsOccurred must beTrue
174
171
  }
175
172
 
176
- "retry CloudFront responds with TooManyInvalidationsInProgressException" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
173
+ "retry CloudFront responds with TooManyInvalidationsInProgressException" in new BasicSetup {
177
174
  setTooManyInvalidationsInProgress(4)
178
175
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
179
176
  setLocalFile("test.css")
@@ -182,7 +179,7 @@ class S3WebsiteSpec extends Specification {
182
179
  sentInvalidationRequests.length must equalTo(4)
183
180
  }
184
181
 
185
- "retry if CloudFront is temporarily unreachable" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
182
+ "retry if CloudFront is temporarily unreachable" in new BasicSetup {
186
183
  invalidationsFailAndThenSucceed(5)
187
184
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
188
185
  setLocalFile("test.css")
@@ -191,7 +188,7 @@ class S3WebsiteSpec extends Specification {
191
188
  sentInvalidationRequests.length must equalTo(6)
192
189
  }
193
190
 
194
- "encode unsafe characters in the keys" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
191
+ "encode unsafe characters in the keys" in new BasicSetup {
195
192
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
196
193
  setLocalFile("articles/arnold's file.html")
197
194
  setOutdatedS3Keys("articles/arnold's file.html")
@@ -199,7 +196,7 @@ class S3WebsiteSpec extends Specification {
199
196
  sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/articles/arnold's%20file.html" :: Nil).sorted)
200
197
  }
201
198
 
202
- "invalidate the root object '/' if a top-level object is updated or deleted" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
199
+ "invalidate the root object '/' if a top-level object is updated or deleted" in new BasicSetup {
203
200
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
204
201
  setLocalFile("maybe-index.html")
205
202
  setOutdatedS3Keys("maybe-index.html")
@@ -209,7 +206,7 @@ class S3WebsiteSpec extends Specification {
209
206
  }
210
207
 
211
208
  "cloudfront_invalidate_root: true" should {
212
- "convert CloudFront invalidation paths with the '/index.html' suffix into '/'" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
209
+ "convert CloudFront invalidation paths with the '/index.html' suffix into '/'" in new BasicSetup {
213
210
  config = """
214
211
  |cloudfront_distribution_id: EGM1J2JJX9Z
215
212
  |cloudfront_invalidate_root: true
@@ -222,7 +219,7 @@ class S3WebsiteSpec extends Specification {
222
219
  }
223
220
 
224
221
  "a site with over 1000 items" should {
225
- "split the CloudFront invalidation requests into batches of 1000 items" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
222
+ "split the CloudFront invalidation requests into batches of 1000 items" in new BasicSetup {
226
223
  val files = (1 to 1002).map { i => s"lots-of-files/file-$i"}
227
224
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
228
225
  setLocalFiles(files:_*)
@@ -235,18 +232,18 @@ class S3WebsiteSpec extends Specification {
235
232
  }
236
233
 
237
234
  "push exit status" should {
238
- "be 0 all uploads succeed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
235
+ "be 0 all uploads succeed" in new BasicSetup {
239
236
  setLocalFiles("file.txt")
240
237
  push must equalTo(0)
241
238
  }
242
239
 
243
- "be 1 if any of the uploads fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
240
+ "be 1 if any of the uploads fails" in new BasicSetup {
244
241
  setLocalFiles("file.txt")
245
242
  when(amazonS3Client.putObject(Matchers.any(classOf[PutObjectRequest]))).thenThrow(new AmazonServiceException("AWS failed"))
246
243
  push must equalTo(1)
247
244
  }
248
245
 
249
- "be 1 if any of the redirects fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
246
+ "be 1 if any of the redirects fails" in new BasicSetup {
250
247
  config = """
251
248
  |redirects:
252
249
  | index.php: /index.html
@@ -255,14 +252,14 @@ class S3WebsiteSpec extends Specification {
255
252
  push must equalTo(1)
256
253
  }
257
254
 
258
- "be 0 if CloudFront invalidations and uploads succeed"in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
255
+ "be 0 if CloudFront invalidations and uploads succeed"in new BasicSetup {
259
256
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
260
257
  setLocalFile("test.css")
261
258
  setOutdatedS3Keys("test.css")
262
259
  push must equalTo(0)
263
260
  }
264
261
 
265
- "be 1 if CloudFront is unreachable or broken"in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
262
+ "be 1 if CloudFront is unreachable or broken"in new BasicSetup {
266
263
  setCloudFrontAsInternallyBroken()
267
264
  config = "cloudfront_distribution_id: EGM1J2JJX9Z"
268
265
  setLocalFile("test.css")
@@ -270,19 +267,19 @@ class S3WebsiteSpec extends Specification {
270
267
  push must equalTo(1)
271
268
  }
272
269
 
273
- "be 0 if upload retry succeeds" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
270
+ "be 0 if upload retry succeeds" in new BasicSetup {
274
271
  setLocalFile("index.html")
275
272
  uploadFailsAndThenSucceeds(howManyFailures = 1)
276
273
  push must equalTo(0)
277
274
  }
278
275
 
279
- "be 1 if delete retry fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
276
+ "be 1 if delete retry fails" in new BasicSetup {
280
277
  setLocalFile("index.html")
281
278
  uploadFailsAndThenSucceeds(howManyFailures = 6)
282
279
  push must equalTo(1)
283
280
  }
284
281
 
285
- "be 1 if an object listing fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
282
+ "be 1 if an object listing fails" in new BasicSetup {
286
283
  setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
287
284
  objectListingFailsAndThenSucceeds(howManyFailures = 6)
288
285
  push must equalTo(1)
@@ -290,7 +287,7 @@ class S3WebsiteSpec extends Specification {
290
287
  }
291
288
 
292
289
  "s3_website.yml file" should {
293
- "never be uploaded" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
290
+ "never be uploaded" in new BasicSetup {
294
291
  setLocalFile("s3_website.yml")
295
292
  push
296
293
  noUploadsOccurred must beTrue
@@ -298,7 +295,7 @@ class S3WebsiteSpec extends Specification {
298
295
  }
299
296
 
300
297
  ".env file" should { // The .env file is the https://github.com/bkeepers/dotenv file
301
- "never be uploaded" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
298
+ "never be uploaded" in new BasicSetup {
302
299
  setLocalFile(".env")
303
300
  push
304
301
  noUploadsOccurred must beTrue
@@ -306,7 +303,7 @@ class S3WebsiteSpec extends Specification {
306
303
  }
307
304
 
308
305
  "exclude_from_upload: string" should {
309
- "result in matching files not being uploaded" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
306
+ "result in matching files not being uploaded" in new BasicSetup {
310
307
  config = "exclude_from_upload: .DS_.*?"
311
308
  setLocalFile(".DS_Store")
312
309
  push
@@ -319,7 +316,7 @@ class S3WebsiteSpec extends Specification {
319
316
  - regex
320
317
  - another_exclusion
321
318
  """ should {
322
- "result in matching files not being uploaded" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
319
+ "result in matching files not being uploaded" in new BasicSetup {
323
320
  config = """
324
321
  |exclude_from_upload:
325
322
  | - .DS_.*?
@@ -332,14 +329,14 @@ class S3WebsiteSpec extends Specification {
332
329
  }
333
330
 
334
331
  "ignore_on_server: value" should {
335
- "not delete the S3 objects that match the ignore value" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
332
+ "not delete the S3 objects that match the ignore value" in new BasicSetup {
336
333
  config = "ignore_on_server: logs"
337
334
  setS3Files(S3File("logs/log.txt", ""))
338
335
  push
339
336
  noDeletesOccurred must beTrue
340
337
  }
341
338
 
342
- "support non-US-ASCII files" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
339
+ "support non-US-ASCII files" in new BasicSetup {
343
340
  setS3Files(S3File("tags/笔记/test.html", ""))
344
341
  config = "ignore_on_server: tags/笔记/test.html"
345
342
  push
@@ -352,7 +349,7 @@ class S3WebsiteSpec extends Specification {
352
349
  - regex
353
350
  - another_ignore
354
351
  """ should {
355
- "not delete the S3 objects that match the ignore value" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
352
+ "not delete the S3 objects that match the ignore value" in new BasicSetup {
356
353
  config = """
357
354
  |ignore_on_server:
358
355
  | - .*txt
@@ -362,7 +359,7 @@ class S3WebsiteSpec extends Specification {
362
359
  noDeletesOccurred must beTrue
363
360
  }
364
361
 
365
- "support non-US-ASCII files" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
362
+ "support non-US-ASCII files" in new BasicSetup {
366
363
  setS3Files(S3File("tags/笔记/test.html", ""))
367
364
  config = """
368
365
  |ignore_on_server:
@@ -373,15 +370,51 @@ class S3WebsiteSpec extends Specification {
373
370
  }
374
371
  }
375
372
 
373
+ "site in config" should {
374
+ "let the user deploy a site from a custom location" in new CustomSiteDirectory with EmptySite with MockAWS with DefaultRunMode {
375
+ config = s"site: $siteDirectory"
376
+ setLocalFile(".vimrc")
377
+
378
+ new File(siteDirectory, ".vimrc").exists() must beTrue // Sanity check
379
+ siteDirectory must not equalTo workingDirectory // Sanity check
380
+
381
+ push
382
+ sentPutObjectRequest.getKey must equalTo(".vimrc")
383
+ }
384
+
385
+ "not override the --site command-line switch" in new BasicSetup {
386
+ config = s"site: dir-that-does-not-exist"
387
+ setLocalFile(".vimrc") // This creates a file in the directory into which the --site CLI arg points
388
+ push
389
+ sentPutObjectRequest.getKey must equalTo(".vimrc")
390
+ }
391
+
392
+ automaticallySupportedSiteGenerators foreach { siteGenerator =>
393
+ "override an automatically detected site" in new CustomSiteDirectory with EmptySite with MockAWS with DefaultRunMode {
394
+ addContentToAutomaticallyDetectedSite(workingDirectory)
395
+ config = s"site: $siteDirectory"
396
+ setLocalFile(".vimrc") // Add content to the custom site directory
397
+ push
398
+ sentPutObjectRequest.getKey must equalTo(".vimrc")
399
+ }
400
+
401
+ def addContentToAutomaticallyDetectedSite(workingDirectory: File) {
402
+ val automaticallyDetectedSiteDir = new File(workingDirectory, siteGenerator.outputDirectory)
403
+ automaticallyDetectedSiteDir.mkdirs()
404
+ write(new File(automaticallyDetectedSiteDir, ".bashrc"), "echo hello")
405
+ }
406
+ }
407
+ }
408
+
376
409
  "max-age in config" can {
377
- "be applied to all files" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
410
+ "be applied to all files" in new BasicSetup {
378
411
  config = "max_age: 60"
379
412
  setLocalFile("index.html")
380
413
  push
381
414
  sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=60")
382
415
  }
383
416
 
384
- "be applied to files that match the glob" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
417
+ "be applied to files that match the glob" in new BasicSetup {
385
418
  config = """
386
419
  |max_age:
387
420
  | "*.html": 90
@@ -391,7 +424,7 @@ class S3WebsiteSpec extends Specification {
391
424
  sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=90")
392
425
  }
393
426
 
394
- "be applied to directories that match the glob" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
427
+ "be applied to directories that match the glob" in new BasicSetup {
395
428
  config = """
396
429
  |max_age:
397
430
  | "assets/**/*.js": 90
@@ -401,7 +434,7 @@ class S3WebsiteSpec extends Specification {
401
434
  sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=90")
402
435
  }
403
436
 
404
- "not be applied if the glob doesn't match" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
437
+ "not be applied if the glob doesn't match" in new BasicSetup {
405
438
  config = """
406
439
  |max_age:
407
440
  | "*.js": 90
@@ -411,14 +444,14 @@ class S3WebsiteSpec extends Specification {
411
444
  sentPutObjectRequest.getMetadata.getCacheControl must beNull
412
445
  }
413
446
 
414
- "be used to disable caching" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
447
+ "be used to disable caching" in new BasicSetup {
415
448
  config = "max_age: 0"
416
449
  setLocalFile("index.html")
417
450
  push
418
451
  sentPutObjectRequest.getMetadata.getCacheControl must equalTo("no-cache; max-age=0")
419
452
  }
420
453
 
421
- "support non-US-ASCII directory names" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
454
+ "support non-US-ASCII directory names" in new BasicSetup {
422
455
  config = """
423
456
  |max_age:
424
457
  | "*": 21600
@@ -429,7 +462,7 @@ class S3WebsiteSpec extends Specification {
429
462
  }
430
463
 
431
464
  "max-age in config" should {
432
- "respect the more specific glob" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
465
+ "respect the more specific glob" in new BasicSetup {
433
466
  config = """
434
467
  |max_age:
435
468
  | "assets/*": 150
@@ -443,7 +476,7 @@ class S3WebsiteSpec extends Specification {
443
476
  }
444
477
 
445
478
  "s3_reduced_redundancy: true in config" should {
446
- "result in uploads being marked with reduced redundancy" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
479
+ "result in uploads being marked with reduced redundancy" in new BasicSetup {
447
480
  config = "s3_reduced_redundancy: true"
448
481
  setLocalFile("file.exe")
449
482
  push
@@ -452,7 +485,7 @@ class S3WebsiteSpec extends Specification {
452
485
  }
453
486
 
454
487
  "s3_reduced_redundancy: false in config" should {
455
- "result in uploads being marked with the default storage class" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
488
+ "result in uploads being marked with the default storage class" in new BasicSetup {
456
489
  config = "s3_reduced_redundancy: false"
457
490
  setLocalFile("file.exe")
458
491
  push
@@ -461,7 +494,7 @@ class S3WebsiteSpec extends Specification {
461
494
  }
462
495
 
463
496
  "redirect in config" should {
464
- "result in a redirect instruction that is sent to AWS" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
497
+ "result in a redirect instruction that is sent to AWS" in new BasicSetup {
465
498
  config = """
466
499
  |redirects:
467
500
  | index.php: /index.html
@@ -470,7 +503,7 @@ class S3WebsiteSpec extends Specification {
470
503
  sentPutObjectRequest.getRedirectLocation must equalTo("/index.html")
471
504
  }
472
505
 
473
- "add slash to the redirect target" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
506
+ "add slash to the redirect target" in new BasicSetup {
474
507
  config = """
475
508
  |redirects:
476
509
  | index.php: index.html
@@ -479,7 +512,7 @@ class S3WebsiteSpec extends Specification {
479
512
  sentPutObjectRequest.getRedirectLocation must equalTo("/index.html")
480
513
  }
481
514
 
482
- "support external redirects" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
515
+ "support external redirects" in new BasicSetup {
483
516
  config = """
484
517
  |redirects:
485
518
  | index.php: http://www.youtube.com/watch?v=dQw4w9WgXcQ
@@ -488,7 +521,7 @@ class S3WebsiteSpec extends Specification {
488
521
  sentPutObjectRequest.getRedirectLocation must equalTo("http://www.youtube.com/watch?v=dQw4w9WgXcQ")
489
522
  }
490
523
 
491
- "support external redirects that point to an HTTPS target" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
524
+ "support external redirects that point to an HTTPS target" in new BasicSetup {
492
525
  config = """
493
526
  |redirects:
494
527
  | index.php: https://www.youtube.com/watch?v=dQw4w9WgXcQ
@@ -497,7 +530,7 @@ class S3WebsiteSpec extends Specification {
497
530
  sentPutObjectRequest.getRedirectLocation must equalTo("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
498
531
  }
499
532
 
500
- "result in max-age=0 Cache-Control header on the object" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
533
+ "result in max-age=0 Cache-Control header on the object" in new BasicSetup {
501
534
  config = """
502
535
  |redirects:
503
536
  | index.php: /index.html
@@ -508,7 +541,7 @@ class S3WebsiteSpec extends Specification {
508
541
  }
509
542
 
510
543
  "redirect in config and an object on the S3 bucket" should {
511
- "not result in the S3 object being deleted" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
544
+ "not result in the S3 object being deleted" in new BasicSetup {
512
545
  config = """
513
546
  |redirects:
514
547
  | index.php: /index.html
@@ -521,7 +554,7 @@ class S3WebsiteSpec extends Specification {
521
554
  }
522
555
 
523
556
  "dotfiles" should {
524
- "be included in the pushed files" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
557
+ "be included in the pushed files" in new BasicSetup {
525
558
  setLocalFile(".vimrc")
526
559
  push
527
560
  sentPutObjectRequest.getKey must equalTo(".vimrc")
@@ -529,25 +562,25 @@ class S3WebsiteSpec extends Specification {
529
562
  }
530
563
 
531
564
  "content type inference" should {
532
- "add charset=utf-8 to all html documents" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
565
+ "add charset=utf-8 to all html documents" in new BasicSetup {
533
566
  setLocalFile("index.html")
534
567
  push
535
568
  sentPutObjectRequest.getMetadata.getContentType must equalTo("text/html; charset=utf-8")
536
569
  }
537
570
 
538
- "add charset=utf-8 to all text documents" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
571
+ "add charset=utf-8 to all text documents" in new BasicSetup {
539
572
  setLocalFile("index.txt")
540
573
  push
541
574
  sentPutObjectRequest.getMetadata.getContentType must equalTo("text/plain; charset=utf-8")
542
575
  }
543
576
 
544
- "add charset=utf-8 to all json documents" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
577
+ "add charset=utf-8 to all json documents" in new BasicSetup {
545
578
  setLocalFile("data.json")
546
579
  push
547
580
  sentPutObjectRequest.getMetadata.getContentType must equalTo("application/json; charset=utf-8")
548
581
  }
549
582
 
550
- "resolve the content type from file contents" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
583
+ "resolve the content type from file contents" in new BasicSetup {
551
584
  setLocalFileWithContent(("index", "<html><body><h1>hi</h1></body></html>"))
552
585
  push
553
586
  sentPutObjectRequest.getMetadata.getContentType must equalTo("text/html; charset=utf-8")
@@ -555,7 +588,7 @@ class S3WebsiteSpec extends Specification {
555
588
  }
556
589
 
557
590
  "ERB in config file" should {
558
- "be evaluated" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
591
+ "be evaluated" in new BasicSetup {
559
592
  config = """
560
593
  |redirects:
561
594
  |<%= ('a'..'f').to_a.map do |t| ' '+t+ ': /'+t+'.html' end.join('\n')%>
@@ -567,14 +600,14 @@ class S3WebsiteSpec extends Specification {
567
600
  }
568
601
 
569
602
  "dry run" should {
570
- "not push updates" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
603
+ "not push updates" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
571
604
  setLocalFileWithContent(("index.html", "<div>new</div>"))
572
605
  setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
573
606
  push
574
607
  noUploadsOccurred must beTrue
575
608
  }
576
609
 
577
- "not push redirects" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
610
+ "not push redirects" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
578
611
  config =
579
612
  """
580
613
  |redirects:
@@ -584,19 +617,19 @@ class S3WebsiteSpec extends Specification {
584
617
  noUploadsOccurred must beTrue
585
618
  }
586
619
 
587
- "not push deletes" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
620
+ "not push deletes" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
588
621
  setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
589
622
  push
590
623
  noUploadsOccurred must beTrue
591
624
  }
592
625
 
593
- "not push new files" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
626
+ "not push new files" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
594
627
  setLocalFile("index.html")
595
628
  push
596
629
  noUploadsOccurred must beTrue
597
630
  }
598
-
599
- "not invalidate files" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
631
+
632
+ "not invalidate files" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
600
633
  config = "cloudfront_invalidation_id: AABBCC"
601
634
  setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
602
635
  push
@@ -619,6 +652,8 @@ class S3WebsiteSpec extends Specification {
619
652
  sentPutObjectRequests.length must equalTo(1)
620
653
  }
621
654
  }
655
+
656
+ trait BasicSetup extends SiteLocationFromCliArg with EmptySite with MockAWS with DefaultRunMode
622
657
 
623
658
  trait DefaultRunMode {
624
659
  implicit def pushMode: PushMode = new PushMode {
@@ -633,7 +668,7 @@ class S3WebsiteSpec extends Specification {
633
668
  }
634
669
 
635
670
  trait MockAWS extends MockS3 with MockCloudFront with Scope
636
-
671
+
637
672
  trait MockCloudFront extends MockAWSHelper {
638
673
  val amazonCloudFrontClient = mock(classOf[AmazonCloudFront])
639
674
  implicit val cfSettings: CloudFrontSetting = CloudFrontSetting(
@@ -677,7 +712,7 @@ class S3WebsiteSpec extends Specification {
677
712
  when(amazonCloudFrontClient.createInvalidation(Matchers.anyObject())).thenThrow(new AmazonServiceException("CloudFront is down"))
678
713
  }
679
714
  }
680
-
715
+
681
716
  trait MockS3 extends MockAWSHelper {
682
717
  val amazonS3Client = mock(classOf[AmazonS3])
683
718
  implicit val s3Settings: S3Setting = S3Setting(
@@ -754,7 +789,7 @@ class S3WebsiteSpec extends Specification {
754
789
  true // Mockito is based on exceptions
755
790
  }
756
791
 
757
- type S3Key = String
792
+ type S3Key = String
758
793
  }
759
794
 
760
795
  trait MockAWSHelper {
@@ -770,8 +805,10 @@ class S3WebsiteSpec extends Specification {
770
805
  }
771
806
 
772
807
  trait Directories extends BeforeAfter {
773
- implicit final val workingDirectory: File = new File(FileUtils.getTempDirectory, "s3_website_dir" + Random.nextLong())
774
- val siteDirectory: File // Represents the --site=X option
808
+ def randomDir() = new File(FileUtils.getTempDirectory, "s3_website_dir" + Random.nextLong())
809
+ implicit final val workingDirectory: File = randomDir()
810
+ implicit def yamlConfig: S3_website_yml = S3_website_yml(new File(workingDirectory, "s3_website.yml"))
811
+ val siteDirectory: File
775
812
  val configDirectory: File = workingDirectory // Represents the --config-dir=X option
776
813
 
777
814
  def before {
@@ -785,22 +822,28 @@ class S3WebsiteSpec extends Specification {
785
822
  }
786
823
  }
787
824
 
788
- /**
789
- * Represents the situation where the current working directory, site dir and config dir are in the same directory
790
- */
791
- trait AllInSameDirectory extends Directories {
825
+ trait SiteLocationFromCliArg extends Directories {
792
826
  val siteDirectory = workingDirectory
827
+ val siteDirFromCLIArg = true
793
828
  }
794
829
 
795
830
  trait JekyllSite extends Directories {
796
831
  val siteDirectory = new File(workingDirectory, "_site")
832
+ val siteDirFromCLIArg = false
797
833
  }
798
834
 
799
835
  trait NanocSite extends Directories {
800
836
  val siteDirectory = new File(workingDirectory, "public/output")
837
+ val siteDirFromCLIArg = false
801
838
  }
802
-
839
+
840
+ trait CustomSiteDirectory extends Directories {
841
+ val siteDirectory = randomDir()
842
+ val siteDirFromCLIArg = false
843
+ }
844
+
803
845
  trait EmptySite extends Directories {
846
+ val siteDirFromCLIArg: Boolean
804
847
  type LocalFileWithContent = (String, String)
805
848
 
806
849
  def setLocalFile(fileName: String) = setLocalFileWithContent((fileName, ""))
@@ -812,6 +855,7 @@ class S3WebsiteSpec extends Specification {
812
855
  file.createNewFile()
813
856
  write(file, fileNameAndContent._2)
814
857
  }
858
+
815
859
  var config = ""
816
860
  val baseConfig =
817
861
  """
@@ -820,34 +864,38 @@ class S3WebsiteSpec extends Specification {
820
864
  |s3_bucket: bucket
821
865
  """.stripMargin
822
866
 
823
- implicit def cliArgs: CliArgs = buildCliArgs(config)
824
- def pushMode: PushMode // Represents the --dry-run switch
825
-
826
- private def buildCliArgs(
827
- config: String = "",
828
- baseConfig: String =
829
- """
830
- |s3_id: foo
831
- |s3_secret: bar
832
- |s3_bucket: bucket
833
- """.stripMargin
834
- ): CliArgs = {
835
- val configFile = new File(configDirectory, "s3_website.yml")
836
- write(configFile,
867
+ implicit def configString: ConfigString =
868
+ ConfigString(
837
869
  s"""
838
870
  |$baseConfig
839
871
  |$config
840
872
  """.stripMargin
841
873
  )
874
+
875
+ def pushMode: PushMode // Represents the --dry-run switch
876
+
877
+ implicit def cliArgs: CliArgs =
842
878
  new CliArgs {
843
- override def verbose = true
879
+ def verbose = true
844
880
 
845
- override def dryRun = pushMode.dryRun
881
+ def dryRun = pushMode.dryRun
846
882
 
847
- override def site = siteDirectory.getAbsolutePath
883
+ def site = if (siteDirFromCLIArg) siteDirectory.getAbsolutePath else null
848
884
 
849
- override def configDir = configDirectory.getAbsolutePath
885
+ def configDir = configDirectory.getAbsolutePath
850
886
  }
851
- }
852
887
  }
888
+
889
+ def push(implicit
890
+ emptyYamlConfig: S3_website_yml,
891
+ configString: ConfigString,
892
+ cliArgs: CliArgs,
893
+ s3Settings: S3Setting,
894
+ cloudFrontSettings: CloudFrontSetting,
895
+ workingDirectory: File) = {
896
+ write(emptyYamlConfig.file, configString.yaml) // Write the yaml config lazily, so that the tests can override the default yaml config
897
+ Push.push
898
+ }
899
+
900
+ case class ConfigString(yaml: String)
853
901
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: s3_website
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.16
4
+ version: 2.2.0
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-07-06 00:00:00.000000000 Z
11
+ date: 2014-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -126,8 +126,8 @@ files:
126
126
  - resources/s3_website.jar.md5
127
127
  - s3_website.gemspec
128
128
  - sbt
129
- - src/main/java/s3/website/ByteHelper.java
130
129
  - src/main/resources/log4j.properties
130
+ - src/main/scala/s3/website/ByteHelper.scala
131
131
  - src/main/scala/s3/website/CloudFront.scala
132
132
  - src/main/scala/s3/website/Diff.scala
133
133
  - src/main/scala/s3/website/Logger.scala
@@ -1,14 +0,0 @@
1
- package s3.website;
2
-
3
- public class ByteHelper {
4
-
5
- // Adapted from http://stackoverflow.com/a/3758880/219947
6
- public static String humanReadableByteCount(long bytes) {
7
- boolean si = true;
8
- int unit = si ? 1000 : 1024;
9
- if (bytes < unit) return bytes + " B";
10
- int exp = (int) (Math.log(bytes) / Math.log(unit));
11
- String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i");
12
- return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
13
- }
14
- }