1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// (C) Copyright 2019 Hewlett Packard Enterprise Development LP

use crate::image::ImageRef;
use crate::parser::{Pair, Rule};
use crate::splicer::*;
use crate::error::*;

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct FromInstruction {
  pub span: Span,
  pub image: String,
  pub image_span: Span,
  pub image_parsed: ImageRef,

  pub index: usize,
  pub alias: Option<String>,
  pub alias_span: Option<Span>
}

impl FromInstruction {
  pub(crate) fn from_record(record: Pair, index: usize) -> Result<FromInstruction> {
    let span = Span::from_pair(&record);
    let mut image_field = None;
    let mut alias_field = None;

    for field in record.into_inner() {
      match field.as_rule() {
        Rule::from_image => image_field = Some(field),
        Rule::from_alias => alias_field = Some(field),
        _ => return Err(unexpected_token(field))
      };
    }

    let (image, image_span) = if let Some(image_field) = image_field {
      (image_field.as_str().to_string(), Span::from_pair(&image_field))
    } else {
      return Err(Error::GenericParseError {
        message: "missing from image".into()
      });
    };

    let image_parsed = ImageRef::parse(&image);

    let (alias, alias_span) = if let Some(alias_field) = alias_field {
      (
        Some(alias_field.as_str().to_string()),
        Some(Span::from_pair(&alias_field))
      )
    } else {
      (None, None)
    };

    Ok(FromInstruction {
      span, index,
      image, image_span, image_parsed,
      alias, alias_span,
    })
  }

  // TODO: util for converting to an ImageRef while resolving ARG
  // per the docs, ARG instructions are only honored in FROMs if they occur
  // before the *first* FROM (but this should be verified)
  // fn image_ref(&self) -> ImageRef { ... }
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::test_util::*;

  #[test]
  fn from_no_alias() -> Result<()> {
    // pulling the FromInstruction out of the enum is messy, so just parse
    // directly
    let from = parse_direct(
      "from alpine:3.10",
      Rule::from,
      |p| FromInstruction::from_record(p, 0)
    )?;
    
    assert_eq!(from, FromInstruction {
      span: Span { start: 0, end: 16 },
      index: 0,
      image: "alpine:3.10".into(),
      image_span: Span { start: 5, end: 16 },
      image_parsed: ImageRef {
        registry: None,
        image: "alpine".into(),
        tag: Some("3.10".into())
      },
      alias: None,
      alias_span: None
    });

    Ok(())
  }

  #[test]
  fn from_no_newline() -> Result<()> {
    // unfortunately we can't use a single rule to test these as individual
    // rules have no ~ EOI requirement to ensure we parse the whole string
    assert!(parse_single(
      "from alpine:3.10 from example",
      Rule::dockerfile,
    ).is_err());

    Ok(())
  }

  #[test]
  fn from_missing_alias() -> Result<()> {
    assert!(parse_single(
      "from alpine:3.10 as",
      Rule::dockerfile,
    ).is_err());

    Ok(())
  }
}