s3_website 2.1.16 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
- }