Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
manabedaiki committed Dec 13, 2017
0 parents commit 7c6707f
Show file tree
Hide file tree
Showing 29 changed files with 1,182 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.vs/
bin/
obj/
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM microsoft/dotnet:sdk AS build

ADD ./ /source

WORKDIR /source

RUN dotnet publish --configuration=Release

FROM microsoft/dotnet:runtime

COPY --from=build /source/PainlessGanttCli/bin/Release/netcoreapp2.0/publish /opt/PainlessGanttCli

ENTRYPOINT ["dotnet", "/opt/PainlessGanttCli/PainlessGanttCli.dll"]
45 changes: 45 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
build:
@docker container run \
--rm \
--interactive \
--tty \
-e UID=$(shell id --user $$USER) \
-e GID=$(shell id --group $$USER) \
-v $$PWD:$$PWD \
--workdir $$PWD \
microsoft/dotnet:sdk sh -c '\
dotnet publish --configuration=Release; \
chown -R $$UID:$$GID PainlessGantt/bin; \
chown -R $$UID:$$GID PainlessGantt/obj; \
chown -R $$UID:$$GID PainlessGanttCli/bin; \
chown -R $$UID:$$GID PainlessGanttCli/obj;'

run:
@[ -f PainlessGanttCli/bin/Release/netcoreapp2.0/publish/PainlessGanttCli.dll ] || make --no-print-directory build
@docker container run \
--rm \
--interactive \
--tty \
-e UID=$(shell id --user $$USER) \
-e GID=$(shell id --group $$USER) \
-v $$PWD/PainlessGanttCli/bin/Release/netcoreapp2.0/publish:/publish:ro \
-v $$PWD/assets:/assets \
--workdir $$PWD \
microsoft/dotnet:runtime sh -c '\
dotnet /publish/PainlessGanttCli.dll \
--project /assets/project.yml \
--template /assets/template.xlsx \
--output /assets/ganttchart.xlsx; \
chown $$UID:$$GID /assets/ganttchart.xlsx;'

format:
@find . -type d \( \
-name .git -o \
-name .vs -o \
-name bin -o \
-name obj \
\) -prune -o -type f ! \( \
-name '*.xlsx' \
\) -print0 | xargs -0 dos2unix

.PHONY: build run format
31 changes: 31 additions & 0 deletions PainlessGantt.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2008
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PainlessGantt", "PainlessGantt\PainlessGantt.csproj", "{FBCF81F7-EFD5-4427-B1C4-953033F2B802}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PainlessGanttCli", "PainlessGanttCli\PainlessGanttCli.csproj", "{C411FB5C-6680-49D5-BE52-C47147CC1607}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FBCF81F7-EFD5-4427-B1C4-953033F2B802}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FBCF81F7-EFD5-4427-B1C4-953033F2B802}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FBCF81F7-EFD5-4427-B1C4-953033F2B802}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FBCF81F7-EFD5-4427-B1C4-953033F2B802}.Release|Any CPU.Build.0 = Release|Any CPU
{C411FB5C-6680-49D5-BE52-C47147CC1607}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C411FB5C-6680-49D5-BE52-C47147CC1607}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C411FB5C-6680-49D5-BE52-C47147CC1607}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C411FB5C-6680-49D5-BE52-C47147CC1607}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0F58CC10-92A7-4015-A4A6-09547AB4BA0F}
EndGlobalSection
EndGlobal
21 changes: 21 additions & 0 deletions PainlessGantt/Construction/ConfigurationBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using YamlDotNet.Serialization;

namespace PainlessGantt.Construction
{
/// <summary>
/// <see cref="IConfiguration"/> インターフェイスの読み書き可能な実装を提供します。
/// </summary>
public sealed class ConfigurationBuilder : IConfiguration
{
/// <summary>
/// 祝日の一覧を取得または設定します。
/// </summary>
[YamlMember(Alias = "祝日")]
public List<DateTime> Holidays { get; set; } = new List<DateTime>();

/// <inheritdoc />
IReadOnlyCollection<DateTime> IConfiguration.Holidays => Holidays;
}
}
23 changes: 23 additions & 0 deletions PainlessGantt/Construction/DateRangeBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using YamlDotNet.Serialization;

namespace PainlessGantt.Construction
{
/// <summary>
/// <see cref="IDateRange"/> インターフェイスの読み書き可能な実装を提供します。
/// </summary>
public sealed class DateRangeBuilder : IDateRange
{
/// <summary>
/// 開始日を取得または設定します。
/// </summary>
[YamlMember(Alias = "開始")]
public DateTime Start { get; set; }

/// <summary>
/// 終了日を取得または設定します。
/// </summary>
[YamlMember(Alias = "終了")]
public DateTime End { get; set; }
}
}
33 changes: 33 additions & 0 deletions PainlessGantt/Construction/GanttSourceBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using YamlDotNet.Serialization;

namespace PainlessGantt.Construction
{
/// <summary>
/// <see cref="IGanttSource"/> インターフェイスの読み書き可能な実装を提供します。
/// </summary>
public sealed class GanttSourceBuilder : IGanttSource
{
/// <summary>
/// このガント チャートの設定を取得または設定します。
/// </summary>
[NotNull]
[YamlMember(Alias = "設定")]
public ConfigurationBuilder Configuration { get; set; } = new ConfigurationBuilder();

/// <inheritdoc />
IConfiguration IGanttSource.Configuration => Configuration;

/// <summary>
/// このガント チャートに含まれているプロジェクトの一覧を取得または設定します。
/// </summary>
[ItemNotNull]
[NotNull]
[YamlMember(Alias = "プロジェクト一覧")]
public List<ProjectBuilder> Projects { get; set; } = new List<ProjectBuilder>();

/// <inheritdoc />
IReadOnlyCollection<IProject> IGanttSource.Projects => Projects;
}
}
29 changes: 29 additions & 0 deletions PainlessGantt/Construction/ProjectBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using YamlDotNet.Serialization;

namespace PainlessGantt.Construction
{
/// <summary>
/// <see cref="IProject"/> インターフェイスの読み書き可能な実装を提供します。
/// </summary>
public sealed class ProjectBuilder : IProject
{
/// <summary>
/// このプロジェクトの名前を取得または設定します。
/// </summary>
[YamlMember(Alias = "プロジェクト")]
public string Name { get; set; } = string.Empty;

/// <summary>
/// このプロジェクトに含まれているチケットの一覧を取得または設定します。
/// </summary>
[ItemNotNull]
[NotNull]
[YamlMember(Alias = "作業一覧")]
public List<TicketBuilder> Tickets { get; set; } = new List<TicketBuilder>();

/// <inheritdoc />
IReadOnlyCollection<ITicket> IProject.Tickets => Tickets;
}
}
53 changes: 53 additions & 0 deletions PainlessGantt/Construction/TicketBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using YamlDotNet.Serialization;

namespace PainlessGantt.Construction
{
/// <summary>
/// <see cref="ITicket"/> インターフェイスの読み書き可能な実装を提供します。
/// </summary>
public sealed class TicketBuilder : ITicket
{
/// <summary>
/// このチケットの名前を取得または設定します。
/// </summary>
[YamlMember(Alias = "作業")]
public string Name { get; set; } = string.Empty;

/// <summary>
/// このチケットの消化に必要な見積もり期間を取得または設定します。
/// </summary>
[YamlMember(Alias = "予定")]
public DateRangeBuilder EstimatedPeriod { get; set; } = new DateRangeBuilder();

/// <inheritdoc />
IDateRange ITicket.EstimatedPeriod => EstimatedPeriod;

/// <summary>
/// このチケットの消化にかかった実際の期間を取得または設定します。
/// </summary>
[YamlMember(Alias = "実績")]
public DateRangeBuilder ActualPeriod { get; set; } = new DateRangeBuilder();

/// <inheritdoc />
IDateRange ITicket.ActualPeriod => ActualPeriod;

/// <summary>
/// このチケットの現状の状態を取得または設定します。
/// </summary>
[YamlMember(Alias = "状態")]
public TicketStatus Status { get; set; } = TicketStatus.Unknown;

/// <summary>
/// このチケットの子チケットを取得または設定します。
/// </summary>
[ItemNotNull]
[NotNull]
[YamlMember(Alias = "詳細")]
public List<TicketBuilder> Children { get; set; } = new List<TicketBuilder>();

/// <inheritdoc />
IReadOnlyCollection<ITicket> ITicket.Children => Children;
}
}
70 changes: 70 additions & 0 deletions PainlessGantt/DateRangeExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;

namespace PainlessGantt
{
/// <summary>
/// <see cref="IDateRange"/> インターフェイスを拡張します。
/// </summary>
public static class DateRangeExtension
{
/// <summary>
/// 指定した日付の範囲に含まれている日を列挙します。
/// </summary>
/// <param name="range">日付の範囲。</param>
/// <returns><paramref name="range"/> に含まれているすべての日を順番に含むシーケンス。</returns>
/// <exception cref="ArgumentNullException"><paramref name="range"/> が <c>null</c> です。</exception>
[LinqTunnel]
[NotNull]
public static IEnumerable<DateTime> AsEnumerable([NotNull] this IDateRange range)
{
if (range == null)
throw new ArgumentNullException(nameof(range));
var daysInRange = (range.End - range.Start).Days + 1;
return Enumerable.Range(0, daysInRange).Select(x => range.Start.AddDays(x));
}

/// <summary>
/// 指定した日が範囲内かどうかを判断します。
/// </summary>
/// <param name="range">日付の範囲。</param>
/// <param name="date">範囲内かどうかを判断する対象の日。</param>
/// <returns><paramref name="date"/> が <paramref name="range"/> の範囲内の場合は <c>true</c>。それ以外の場合は <c>false</c>。</returns>
public static bool In([NotNull] this IDateRange range, DateTime date)
{
if (range == null)
throw new ArgumentNullException(nameof(range));
if (range.Start == default || range.End == default)
return false;
return range.Start <= date && date <= range.End;
}

/// <summary>
/// 指定した <see cref="IDateRange"/> のシーケンスに含まれている日付の最小値を取得します。
/// </summary>
/// <param name="source"><see cref="IDateRange"/> のシーケンス。</param>
/// <returns><paramref name="source"/> に含まれている日付の最小値。</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> が <c>null</c> です。</exception>
public static DateTime Minimum([ItemNotNull] [NotNull] this IEnumerable<IDateRange> source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
return source.Where(x => x.Start != default).Select(x => x.Start).DefaultIfEmpty().Min();
}

/// <summary>
/// 指定した <see cref="IDateRange"/> のシーケンスに含まれている日付の最大値を取得します。
/// </summary>
/// <param name="source"><see cref="IDateRange"/> のシーケンス。</param>
/// <returns><paramref name="source"/> に含まれている日付の最大値。</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> が <c>null</c> です。</exception>
public static DateTime Maximum([ItemNotNull] [NotNull] this IEnumerable<IDateRange> source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
return source.Where(x => x.End != default).Select(x => x.End).DefaultIfEmpty().Max();
}
}
}
61 changes: 61 additions & 0 deletions PainlessGantt/EnumDisplayNameAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;

namespace PainlessGantt
{
/// <summary>
/// 列挙体のフィールドをユーザーに表示する際の表示名を定義します。
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public sealed class EnumDisplayNameAttribute : Attribute
{
[NotNull]
private static readonly ConcurrentDictionary<Type, Dictionary<string, string>> _cache = new ConcurrentDictionary<Type, Dictionary<string, string>>();

/// <summary>
/// 指定した列挙値に関連付けられている <see cref="DisplayName"/> を取得します。
/// </summary>
/// <typeparam name="TEnum"><see cref="EnumDisplayNameAttribute"/> 属性が宣言された列挙体の型。</typeparam>
/// <param name="value"><typeparamref name="TEnum"/> 型の列挙値。</param>
/// <returns><paramref name="value"/> に関連付けられている <see cref="DisplayName"/>。</returns>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> が列挙体ではありません。
/// または、<paramref name="value"/> に関連付けられている <see cref="EnumDisplayNameAttribute"/> 属性が見つかりませんでした。
/// </exception>
[NotNull]
public static string GetDisplayName<TEnum>(TEnum value)
where TEnum : struct
{
var index = _cache.GetOrAdd(typeof(TEnum), type => type
.GetFields(BindingFlags.Public | BindingFlags.Static)
.Select(x => (name: x.Name, attribute: x.GetCustomAttribute<EnumDisplayNameAttribute>()))
.ToDictionary(x => x.name, x => x.attribute.DisplayName));
var fieldName = Enum.GetName(typeof(TEnum), value);
if (fieldName == null)
throw new ArgumentException("列挙値ではありません。");
if (!index.TryGetValue(fieldName, out var displayName))
throw new FormatException($"列挙値に関連付けられている {nameof(EnumDisplayNameAttribute)} 属性が見つかりませんでした。");
return displayName;
}

/// <summary>
/// <see cref="EnumDisplayNameAttribute"/> クラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="displayName">列挙体の表示名。</param>
/// <exception cref="ArgumentNullException"><paramref name="displayName"/> が <c>null</c> です。</exception>
internal EnumDisplayNameAttribute([NotNull] string displayName)
{
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
}

/// <summary>
/// 列挙体の表示名を取得します。
/// </summary>
[NotNull]
public string DisplayName { get; }
}
}
Loading

0 comments on commit 7c6707f

Please sign in to comment.